Add a macro for building properties outside of html! (#1599)

* remove renamed imports from yew-macro

They add a lot of cognitive overhead and don't provide much benefit in this case.

* just a prototype

* cleanup

* add prop type resolver

* use new props for tags

* silence clippy

* simplify tag parsing

* clean up

* improve names

* fix list span

* new component props parsing

* fix rogue lint

* update tag attribute parsing

* unify prop handling

* add new tests

* integrate prop validation

* improve error span regression

* add docstring

* update tests

* add test for specifying `children` twice

* move properties derive macro

* component transformer documentation

* update properties documentation

* document special properties

* let's try to fix the spellcheck

* let's just use a newer image then

* document `with props` children

* clean up a tad

* is boolean the missing word?
Starting to question the use of this spell checker...

* add the note for the recursion limit back in

* code review

* improve error for duplicate children

* clippyfying

* revert Task: Drop

* HtmlTag -> HtmlElement

* link the issue for prop_or_else

* PropList -> SortedPropList

* use struct syntax

* use html! in transformer demonstration
This commit is contained in:
Simon 2020-10-21 14:05:06 +02:00 committed by GitHub
parent 724ac1d481
commit fa2ab5abe7
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
48 changed files with 2115 additions and 1291 deletions

View File

@ -82,7 +82,8 @@ jobs:
doc_tests:
name: Documentation Tests
runs-on: ubuntu-latest
# Using 20.04 because 18.04 (latest) only has aspell 0.60.7 and we need 0.60.8
runs-on: ubuntu-20.04
steps:
- uses: actions/checkout@v2
- uses: actions-rs/toolchain@v1
@ -103,7 +104,9 @@ jobs:
cargo-${{ runner.os }}-
- name: Check spelling
run: bash ci/spellcheck.sh list
run: |
sudo apt-get install aspell
ci/spellcheck.sh list
- name: Run doctest
uses: actions-rs/cargo@v1

View File

@ -1,20 +1,11 @@
personal_ws-1.1 en 88 utf-8
Angular's
Config
Deref
Github
Json
Lifecycle
React's
Todo
VecDeque
Webpack
personal_ws-1.1 en 0 utf-8
alloc
ALLOC
allocator
Angular's
asmjs
backends
barebones
binaryen
Binaryen
bindgen
bool
boolean
@ -23,47 +14,58 @@ charset
codebase
codegen
composable
Config
declaratively
doctype
defs
DerefMut
DOCTYPE
DOCUSAURUS
emscripten
Emscripten
enum
enums
Github
href
html
Html
IHtmlElement
impl
init
INIT
interop
interoperability
interoperable
lang
libs
lifecycle
Lifecycle
linecap
linejoin
memoized
metaprogramming
minimize
miniserve
mkdir
natively
onclick
proc
React's
README
rlib
roadmap
Roadmap
rollup
rustc
rustfmt
rustwasm
stacktraces
rustup
stdweb
struct
structs
tbody
textarea
thead
TODO
toml
Unselected
usize
vdom
vtag
VecDeque
Vuetify
wasm
Wasm
webpack
Webpack
WeeAlloc
workspaces
vuetify
xmlns
yewtil
Yewtil

View File

@ -60,8 +60,8 @@ if [[ ! -f "$dict_filename" ]]; then
echo "Scanning files to generate dictionary file '$dict_filename'."
echo "Please check that it doesn't contain any misspellings."
echo "personal_ws-1.1 en 0 utf-8" > "$dict_filename"
cat "${markdown_sources[@]}" | aspell --ignore 3 list | sort -u >> "$dict_filename"
echo "personal_ws-1.1 en 0 utf-8" >"$dict_filename"
cat "${markdown_sources[@]}" | aspell --ignore 3 --camel-case list | sort -u >>"$dict_filename"
elif [[ "$mode" == "list" ]]; then
# List (default) mode: scan all files, report errors.
declare -i retval=0
@ -74,7 +74,7 @@ elif [[ "$mode" == "list" ]]; then
fi
for fname in "${markdown_sources[@]}"; do
command=$(aspell --ignore 3 --camel-case --personal="$dict_path" "$mode" < "$fname")
command=$(aspell --ignore 3 --camel-case --personal="$dict_path" "$mode" <"$fname")
if [[ -n "$command" ]]; then
for error in $command; do
# FIXME: find more correct way to get line number
@ -96,6 +96,6 @@ elif [[ "$mode" == "check" ]]; then
fi
for fname in "${markdown_sources[@]}"; do
aspell --ignore 3 --dont-backup --personal="$dict_path" "$mode" "$fname"
aspell --ignore 3 --camel-case --dont-backup --personal="$dict_path" "$mode" "$fname"
done
fi

View File

@ -2,6 +2,7 @@
title: Introduction
description: Components and their lifecycle hooks
---
## What are Components?
Components are the building blocks of Yew. They manage their own state and can render themselves to the DOM. Components are created by implementing the `Component` trait for a type. The `Component`
@ -20,7 +21,7 @@ in the lifecycle of a component.
When a component is created, it receives properties from its parent component as well as a `ComponentLink`. The properties can be used to initialize the component's state and the "link" can be used to register callbacks or send messages to the component.
It is common to store the props (data which can be passed from parent to child components) and the
It is common to store the props (data which can be passed from parent to child components) and the
`ComponentLink` in your component struct, like so:
```rust
@ -43,10 +44,10 @@ impl Component for MyComponent {
### View
The `view` method allows you to describe how a component should be rendered to the DOM. Writing
HTML-like code using Rust functions can become quite messy, so Yew provides a macro called `html!`
for declaring HTML and SVG nodes (as well as attaching attributes and event listeners to them) and a
convenient way to render child components. The macro is somewhat similar to React's JSX (the
The `view` method allows you to describe how a component should be rendered to the DOM. Writing
HTML-like code using Rust functions can become quite messy, so Yew provides a macro called `html!`
for declaring HTML and SVG nodes (as well as attaching attributes and event listeners to them) and a
convenient way to render child components. The macro is somewhat similar to React's JSX (the
differences in programming language aside).
```rust
@ -66,9 +67,9 @@ For usage details, check out [the `html!` guide](html.md).
### Rendered
The `rendered` component lifecycle method is called once `view` has been called and Yew has rendered
The `rendered` component lifecycle method is called once `view` has been called and Yew has rendered
the results to the DOM, but before the browser refreshes the page. This method is useful when you
want to perform actions that can only be completed after the component has rendered elements. There
want to perform actions that can only be completed after the component has rendered elements. There
is also a parameter called `first_render` which can be used to determine whether this function is
being called on the first render, or instead a subsequent one.
@ -107,8 +108,8 @@ Note that this lifecycle method does not require an implementation and will do n
### Update
Communication with components happens primarily through messages which are handled by the
`update` lifecycle method. This allows the component to update itself
based on what the message was, and determine if it needs to re-render itself. Messages can be sent
`update` lifecycle method. This allows the component to update itself
based on what the message was, and determine if it needs to re-render itself. Messages can be sent
by event listeners, child components, Agents, Services, or Futures.
Here's an example of what an implementation of `update` could look like:
@ -140,8 +141,8 @@ impl Component for MyComponent {
### Change
Components may be re-rendered by their parents. When this happens, they could receive new properties
and need to re-render. This design facilitates parent to child component communication by just
Components may be re-rendered by their parents. When this happens, they could receive new properties
and need to re-render. This design facilitates parent to child component communication by just
changing the values of a property.
A typical implementation would look something like:
@ -163,7 +164,7 @@ impl Component for MyComponent {
### Destroy
After Components are unmounted from the DOM, Yew calls the `destroy` lifecycle method; this is
After Components are unmounted from the DOM, Yew calls the `destroy` lifecycle method; this is
necessary if you need to undertake operations to clean up after earlier actions of a component
before it is destroyed. This method is optional and does nothing by default.
@ -180,15 +181,15 @@ impl Component for MyComponent {
}
```
The `Message` type is used to send messages to a component after an event has taken place; for
example you might want to undertake some action when a user clicks a button or scrolls down the
The `Message` type is used to send messages to a component after an event has taken place; for
example you might want to undertake some action when a user clicks a button or scrolls down the
page. Because components tend to have to respond to more than one event, the `Message` type will
normally be an enum, where each variant is an event to be handled.
When organising your codebase, it is sensible to include the definition of the `Message` type in the
same module in which your component is defined. You may find it helpful to adopt a consistent naming
convention for message types. One option (though not the only one) is to name the types
`ComponentNameMsg`, e.g. if your component was called `Homepage` then you might call the type
When organizing your codebase, it is sensible to include the definition of the `Message` type in the
same module in which your component is defined. You may find it helpful to adopt a consistent naming
convention for message types. One option (though not the only one) is to name the types
`ComponentNameMsg`, e.g. if your component was called `Homepage` then you might call the type
`HomepageMsg`.
```rust

View File

@ -2,23 +2,50 @@
title: Properties
description: Parent to child communication
---
Properties enable child and parent components to communicate with each other.
Every component has an associated properties type which describes what is passed down from the parent.
In theory this can be any type that implements the `Properties` trait, but in practice there's no
reason for it to be anything but a struct where each field represents a property.
## Derive macro
Don't try to implement `Properties` yourself, derive it by using `#[derive(Properties)]` instead.
Instead of implementing the `Properties` trait yourself, you should use `#[derive(Properties)]` to
automatically generate the implementation instead.
Types for which you derive `Properties` must also implement `Clone`.
:::note
Types for which you derive `Properties` must also implement `Clone`. This can be done by either using `#[derive(Properties, Clone)` or manually implementing `Clone` for your type.
### Field attributes
When deriving `Properties`, all fields are required by default.
The following attributes allow you to give your props initial values which will be used unless they're set to another value.
:::tip
Attributes aren't visible in Rustdoc generated documentation.
The docstrings of your properties should mention whether a prop is optional and if it has a special default value.
:::
### Required attributes
#### `#[prop_or_default]`
The fields within a struct that derives `Properties` are required by default. When a field is missing and the component is created in the `html!` macro, a compiler error is returned. For fields with optional properties, use the `#[prop_or_default]` attribute to use the default value for that type when the prop is not specified. To specify a value, use the `#[prop_or(value)]` attribute where value is the default value for the property or alternatively use `#[prop_or_else(function)]` where `function` returns the default value. For example, to default a boolean value as `true`, use the attribute `#[prop_or(true)]`. It is common for optional properties to use the `Option` enum which has the default value `None`.
Initialize the prop value with the default value of the field's type using the `Default` trait.
### PartialEq
#### `#[prop_or(value)]`
It is likely to make sense to derive `PartialEq` on your props if you can do this. Using `PartialEq` makes it much easier to avoid unnecessary rerendering \(this is explained in the **Optimizations & Best Practices** section\).
Use `value` to initialize the prop value. `value` can be any expression that returns the field's type.
For example, to default a boolean prop to `true`, use the attribute `#[prop_or(true)]`.
#### `#[prop_or_else(function)]`
Call `function` to initialize the prop value. `function` should have the signature `FnMut() -> T` where `T` is the field type.
:::warning
The function is currently called even if the prop is explicitly set. If your function is performance intensive, consider using `Option` where `None` values are initialized in the `create` method.
See [#1623](https://github.com/yewstack/yew/issues/1623)
:::
## PartialEq
It makes sense to derive `PartialEq` on your props if you can do so.
Using `PartialEq` makes it much easier to avoid unnecessary rendering \(this is explained in the **Optimizations & Best Practices** section\).
## Memory/speed overhead of using Properties
@ -45,11 +72,8 @@ pub enum LinkColor {
Purple,
}
impl Default for LinkColor {
fn default() -> Self {
// The link color will be blue unless otherwise specified.
LinkColor::Blue
}
fn create_default_link_color() -> LinkColor {
LinkColor::Blue
}
#[derive(Properties, Clone, PartialEq)]
@ -58,9 +82,9 @@ pub struct LinkProps {
href: String,
/// If the link text is huge, this will make copying the string much cheaper.
/// This isn't usually recommended unless performance is known to be a problem.
text: Rc<String>,
/// Color of the link.
#[prop_or_default]
text: Rc<str>,
/// Color of the link. Defaults to `Blue`.
#[prop_or_else(create_default_link_color)]
color: LinkColor,
/// The view function will not specify a size if this is None.
#[prop_or_default]
@ -71,3 +95,23 @@ pub struct LinkProps {
}
```
## Props macro
The `yew::props!` macro allows you to build properties the same way the `html!` macro does it.
The macro uses the same syntax as a struct expression except that you can't use attributes or a base expression (`Foo { ..base }`).
The type path can either point to the props directly (`path::to::Props`) or the associated properties of a component (`MyComp::Properties`).
```rust
let props = yew::props!(LinkProps {
href: "/",
text: Rc::from("imagine this text being really long"),
size: 64,
});
// build the associated properties of a component
let props = yew::props!(Model::Properties {
href: "/book",
text: Rc::from("my bestselling novel"),
});
```

View File

@ -4,21 +4,126 @@ sidebar_label: Introduction
description: The procedural macro for generating HTML and SVG
---
The `html!` macro allows you to write HTML and SVG code declaratively. It is similar to JSX
The `html!` macro allows you to write HTML and SVG code declaratively. It is similar to JSX
\(an extension to JavaScript which allows you to write HTML-like code inside of JavaScript\).
**Important notes**
1. The `html!` macro only accepts one root html node \(you can counteract this by
[using fragments or iterators](html/lists.md)\)
1. The `html!` macro only accepts one root html node \(you can counteract this by
[using fragments or iterators](html/lists.md)\)
2. An empty `html! {}` invocation is valid and will not render anything
3. Literals must always be quoted and wrapped in braces: `html! { "Hello, World" }`
:::note
The `html!` macro can reach easily the default recursion limit of the compiler. It is advised to
bump its value if you encounter compilation errors. Use an attribute like
`#![recursion_limit="1024"]` in the crate root \(i.e. either `lib.rs` or `main.rs`\) to overcome the
problem.
The `html!` macro can reach the default recursion limit of the compiler. If you encounter compilation errors, add an attribute like `#![recursion_limit="1024"]` in the crate root to overcome the problem.
:::
See the [official documentation](https://doc.rust-lang.org/reference/attributes/limits.html#the-recursion_limit-attribute) and [this Stack Overflow question](https://stackoverflow.com/questions/27454761/what-is-a-crate-attribute-and-where-do-i-add-it) for details.
:::
## Tag Structure
Tags are based on HTML tags. Components, Elements, and Lists are all based on this tag syntax.
Tags must either self-close `<... />` or have a corresponding end tag for each start tag.
<!--DOCUSAURUS_CODE_TABS-->
<!--Open - Close-->
```rust
html! {
<div id="my_div"></div>
}
```
<!--Invalid-->
```rust
html! {
<div id="my_div"> // <- MISSING CLOSE TAG
}
```
<!--Self-closing-->
```rust
html! {
<input id="my_input" />
}
```
<!--Invalid-->
```rust
html! {
<input id="my_input"> // <- MISSING SELF-CLOSE
}
```
<!--END_DOCUSAURUS_CODE_TABS-->
:::tip
For convenience, elements which _usually_ require a closing tag are **allowed** to self-close. For example, writing `html! { <div class="placeholder" /> }` is valid.
:::
## Children
Create complex nested HTML and SVG layouts with ease:
<!--DOCUSAURUS_CODE_TABS-->
<!--HTML-->
```rust
html! {
<div>
<div data-key="abc"></div>
<div class="parent">
<span class="child" value="anything"></span>
<label for="first-name">{ "First Name" }</label>
<input type="text" id="first-name" value="placeholder" />
<input type="checkbox" checked=true />
<textarea value="write a story" />
<select name="status">
<option selected=true disabled=false value="">{ "Selected" }</option>
<option selected=false disabled=true value="">{ "Unselected" }</option>
</select>
</div>
</div>
}
```
<!--SVG-->
```rust
html! {
<svg width="149" height="147" viewBox="0 0 149 147" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M60.5776 13.8268L51.8673 42.6431L77.7475 37.331L60.5776 13.8268Z" fill="#DEB819"/>
<path d="M108.361 94.9937L138.708 90.686L115.342 69.8642" stroke="black" stroke-width="4" stroke-linecap="round" stroke-linejoin="round"/>
<g filter="url(#filter0_d)">
<circle cx="75.3326" cy="73.4918" r="55" fill="#FDD630"/>
<circle cx="75.3326" cy="73.4918" r="52.5" stroke="black" stroke-width="5"/>
</g>
<circle cx="71" cy="99" r="5" fill="white" fill-opacity="0.75" stroke="black" stroke-width="3"/>
<defs>
<filter id="filter0_d" x="16.3326" y="18.4918" width="118" height="118" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
<feGaussianBlur stdDeviation="2"/>
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0"/>
</filter>
</defs>
</svg>
}
```
<!--END_DOCUSAURUS_CODE_TABS-->
## Special properties
There are special properties which don't directly influence the DOM but instead act as instructions to Yew's virtual DOM.
Currently, there are two such special props: `ref` and `key`.
`ref` allows you to access and manipulate the underlying DOM node directly. See [Refs](components/refs) for more details.
`key` on the other hand gives an element a unique identifier which Yew can use for optimization purposes.
:::important
The documentation for keys is yet to be written. See [#1263](https://github.com/yewstack/yew/issues/1263).
For now, use keys when you have a list where the order of elements changes. This includes inserting or removing elements from anywhere but the end of the list.
:::

View File

@ -2,6 +2,7 @@
title: Components
description: Create complex layouts with component hierarchies
---
## Basic
Any type that implements `Component` can be used in the `html!` macro:
@ -27,21 +28,38 @@ Components can be passed children if they have a `children` field in their `Prop
```rust title="parent.rs"
html! {
<Container>
<Container id="container">
<h4>{ "Hi" }</h4>
<div>{ "Hello" }</div>
</Container>
}
```
```rust title="container.rs"
pub struct Container(Props);
When using the `with props` syntax, the children passed in the `html!` macro overwrite the ones already present in the props.
```rust
let props = yew::props!(Container::Properties {
id: "container-2",
children: Children::default(),
});
html! {
<Container with props>
// props.children will be overwritten with this
<span>{ "I am a child, as you can see" }</span>
</Container>
}
```
Here's the implementation of `Container`:
```rust
#[derive(Properties, Clone)]
pub struct Props {
pub id: String,
pub children: Children,
}
pub struct Container(Props);
impl Component for Container {
type Properties = Props;
@ -49,7 +67,7 @@ impl Component for Container {
fn view(&self) -> Html {
html! {
<div id="container">
<div id=&self.0.id>
{ self.0.children.clone() }
</div>
}
@ -57,15 +75,11 @@ impl Component for Container {
}
```
:::note
Types for which you derive `Properties` must also implement `Clone`. This can be done by either using `#[derive(Properties, Clone)]` or manually implementing `Clone` for your type.
:::
## Nested Children with Props
Nested component properties can be accessed and mutated if the containing component types its children. In the following example, the `List` component can wrap `ListItem` components. For a real world example of this pattern, check out the `yew-router` source code. For a more advanced example, check out the `nested-list` example in the main yew repository.
```rust title="parent.rs"
```rust
html! {
<List>
<ListItem value="a" />
@ -75,14 +89,13 @@ html! {
}
```
```rust title="list.rs"
pub struct List(Props);
```rust
#[derive(Properties, Clone)]
pub struct Props {
pub children: ChildrenWithProps<ListItem>,
}
pub struct List(Props);
impl Component for List {
type Properties = Props;
@ -99,3 +112,41 @@ impl Component for List {
}
```
## Transformers
Whenever you set a prop its value goes through a transformation step first.
If the value already has the correct type, this step doesn't do anything.
However, transformers can be useful to reduce code repetition.
The following is a list of transformers you should know about:
- `&T` -> `T`
Clones the reference to get an owned value.
- `&str` -> `String`
Allows you to use string literals without adding `.to_owned()` at the end.
- `T` -> `Option<T>`
Wraps the value in `Some`.
```rust
struct Props {
unique_id: Option<usize>,
text: String,
}
struct Model;
impl Component for Model {
type Properties = Props;
// ...
}
// transformers allow you to write this:
html! { <Model unique_id=5 text="literals are fun" /> };
// instead of:
html! { <Model unique_id=Some(5) text="literals are fun".to_owned() /> };
```

View File

@ -2,92 +2,6 @@
title: Elements
description: Both HTML and SVG elements are supported
---
## Tag Structure
Element tags must either self-close `<... />` or have a corresponding close tag for each open tag
<!--DOCUSAURUS_CODE_TABS-->
<!--Open - Close-->
```rust
html! {
<div id="my_div"></div>
}
```
<!--Invalid-->
```rust
html! {
<div id="my_div"> // <- MISSING CLOSE TAG
}
```
<!--Self-closing-->
```rust
html! {
<input id="my_input" />
}
```
<!--Invalid-->
```rust
html! {
<input id="my_input"> // <- MISSING SELF-CLOSE
}
```
<!--END_DOCUSAURUS_CODE_TABS-->
:::note
For convenience, elements which _usually_ require a closing tag are **allowed** to self-close. For example, writing `html! { <div class="placeholder" /> }` is valid.
:::
## Children
Create complex nested HTML and SVG layouts with ease:
<!--DOCUSAURUS_CODE_TABS-->
<!--HTML-->
```rust
html! {
<div>
<div data-key="abc"></div>
<div class="parent">
<span class="child" value="anything"></span>
<label for="first-name">{ "First Name" }</label>
<input type="text" id="first-name" value="placeholder" />
<input type="checkbox" checked=true />
<textarea value="write a story" />
<select name="status">
<option selected=true disabled=false value="">{ "Selected" }</option>
<option selected=false disabled=true value="">{ "Unselected" }</option>
</select>
</div>
</div>
}
```
<!--SVG-->
```rust
html! {
<svg width="149" height="147" viewBox="0 0 149 147" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M60.5776 13.8268L51.8673 42.6431L77.7475 37.331L60.5776 13.8268Z" fill="#DEB819"/>
<path d="M108.361 94.9937L138.708 90.686L115.342 69.8642" stroke="black" stroke-width="4" stroke-linecap="round" stroke-linejoin="round"/>
<g filter="url(#filter0_d)">
<circle cx="75.3326" cy="73.4918" r="55" fill="#FDD630"/>
<circle cx="75.3326" cy="73.4918" r="52.5" stroke="black" stroke-width="5"/>
</g>
<circle cx="71" cy="99" r="5" fill="white" fill-opacity="0.75" stroke="black" stroke-width="3"/>
<defs>
<filter id="filter0_d" x="16.3326" y="18.4918" width="118" height="118" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
<feGaussianBlur stdDeviation="2"/>
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0"/>
</filter>
</defs>
</svg>
}
```
<!--END_DOCUSAURUS_CODE_TABS-->
## Optional attributes for HTML elements
@ -111,6 +25,7 @@ There are a number of convenient ways to specify classes for an element:
<!--DOCUSAURUS_CODE_TABS-->
<!--Literal-->
```rust
html! {
<div class="container"></div>
@ -118,39 +33,37 @@ html! {
```
<!--Multiple-->
```rust
html! {
<div class="container center-align"></div>
}
```
<!--Interpolated-->
```rust
html! {
<div class=format!("{}-container", size)></div>
}
```
<!--Expression-->
```rust
html! {
<div class=self.classes()></div>
}
```
<!--Tuple-->
```rust
html! {
<div class=("class-1", "class-2")></div>
}
```
<!--Optional-->
```rust
html! {
<div class=if condition { Some("class") } else { None } />
}
```
<!--Interpolated-->
```rust
html! {
<div class=format!("{}-container", size) />
}
```
<!--Vector-->
```rust
html! {
<div class=vec!["class-1", "class-2"]></div>
}
```
<!--END_DOCUSAURUS_CODE_TABS-->
## Listeners
@ -159,6 +72,7 @@ Listener attributes need to be passed a `Callback` which is a wrapper around a c
<!--DOCUSAURUS_CODE_TABS-->
<!--Component handler-->
```rust
struct MyComponent {
link: ComponentLink<Self>,
@ -230,6 +144,7 @@ impl Component for MyComponent {
```
<!--Other Cases-->
```rust
struct MyComponent;
@ -259,116 +174,113 @@ impl Component for MyComponent {
}
}
```
<!--END_DOCUSAURUS_CODE_TABS-->
## Event Types
:::note
In the following table `web-sys`'s event types should only be used if you're using `yew` with `web-sys`
(this is enabled by default). Use `stdweb`'s event types if you're using the `yew-stdweb` crate. See
(this is enabled by default). Use `stdweb`'s event types if you're using the `yew-stdweb` crate. See
[the documentation page about whether to choose `web-sys` or `stdweb`](https://yew.rs/docs/getting-started/choose-web-library) for more information.
:::tip
All the event types mentioned in the following table are re-exported under `yew::events`.
Using the types from `yew::events` makes it easier to ensure version compatibility than if you were to manually include `web-sys` or `stdweb` as dependencies in your crate because you won't end up using a version which conflicts with the version Yew specifies.
:::
:::note
All the event types mentioned in the following table are re-exported under `yew::events`. Using the types from
`yew::events` makes it easier to ensure version compatibility than if you were to manually include `web-sys`
or `stdweb` as dependencies in your crate because you won't end up using a version which conflicts with
the version Yew specifies.
:::
| Event name | `web_sys` Event Type | `stdweb` Event Type |
| ----------- | -------------------- | ------------------ |
| `onabort` | [Event](https://docs.rs/web-sys/latest/web_sys/struct.Event.html) | [ResourceAbortEvent](https://docs.rs/stdweb/latest/stdweb/web/event/struct.ResourceAbortEvent.html) |
| `onauxclick` | [MouseEvent](https://docs.rs/web-sys/latest/web_sys/struct.MouseEvent.html) | [AuxClickEvent](https://docs.rs/stdweb/latest/stdweb/web/event/struct.AuxClickEvent.html) |
| `onblur` | [FocusEvent](https://docs.rs/web-sys/latest/web_sys/struct.FocusEvent.html) | [BlurEvent](https://docs.rs/stdweb/latest/stdweb/web/event/struct.BlurEvent.html) |
| `oncancel` | [Event](https://docs.rs/web-sys/latest/web_sys/struct.Event.html) | Unsupported |
| `oncanplay` | [Event](https://docs.rs/web-sys/latest/web_sys/struct.Event.html) | Unsupported |
| `oncanplaythrough` | [Event](https://docs.rs/web-sys/latest/web_sys/struct.Event.html) | Unsupported |
| `onchange` | [ChangeData](https://docs.rs/yew/latest/yew/events/enum.ChangeData.html) | [ChangeData](https://docs.rs/yew-stdweb/latest/yew_stdweb/events/enum.ChangeData.html) |
| `onclick` | [MouseEvent](https://docs.rs/web-sys/latest/web_sys/struct.MouseEvent.html) | [ClickEvent](https://docs.rs/stdweb/latest/stdweb/web/event/struct.ClickEvent.html) |
| `onclose` | [Event](https://docs.rs/web-sys/latest/web_sys/struct.Event.html) | Unsupported |
| `oncontextmenu` | [MouseEvent](https://docs.rs/web-sys/latest/web_sys/struct.MouseEvent.html) | [ContextMenuEvent](https://docs.rs/stdweb/latest/stdweb/web/event/struct.ContextMenuEvent.html) |
| `oncuechange` | [Event](https://docs.rs/web-sys/latest/web_sys/struct.Event.html) | Unsupported |
| `ondblclick` | [MouseEvent](https://docs.rs/web-sys/latest/web_sys/struct.MouseEvent.html) | [DoubleClickEvent](https://docs.rs/stdweb/latest/stdweb/web/event/struct.DoubleClickEvent.html) |
| `ondrag` | [DragEvent](https://docs.rs/web-sys/latest/web_sys/struct.DragEvent.html) | [DragEvent](https://docs.rs/stdweb/latest/stdweb/web/event/struct.DragEvent.html) |
| `ondragend` | [DragEvent](https://docs.rs/web-sys/latest/web_sys/struct.DragEvent.html) | [DragEndEvent](https://docs.rs/stdweb/latest/stdweb/web/event/struct.DragEndEvent.html) |
| `ondragenter` | [DragEvent](https://docs.rs/web-sys/latest/web_sys/struct.DragEvent.html) | [DragEnterEvent](https://docs.rs/stdweb/latest/stdweb/web/event/struct.DragEnterEvent.html) |
| `ondragexit` | [DragEvent](https://docs.rs/web-sys/latest/web_sys/struct.DragEvent.html) | [DragExitEvent](https://docs.rs/stdweb/latest/stdweb/web/event/struct.DragExitEvent.html) |
| `ondragleave` | [DragEvent](https://docs.rs/web-sys/latest/web_sys/struct.DragEvent.htmk) | [DragLeaveEvent](https://docs.rs/stdweb/latest/stdweb/web/event/struct.DragLeaveEvent.html) |
| `ondragover` | [DragEvent](https://docs.rs/web-sys/latest/web_sys/struct.DragEvent.html) | [DragOverEvent](https://docs.rs/stdweb/latest/stdweb/web/event/struct.DragOverEvent.html) |
| `ondragstart` | [DragEvent](https://docs.rs/web-sys/latest/web_sys/struct.DragEvent.html) | [DragStartEvent](https://docs.rs/stdweb/latest/stdweb/web/event/struct.DragStartEvent.html) |
| `ondrop` | [DragEvent](https://docs.rs/web-sys/latest/web_sys/struct.DragEvent.html) | [DragDropEvent](https://docs.rs/stdweb/latest/stdweb/web/event/struct.DragDropEvent.html) |
| `ondurationchange` | [Event](https://docs.rs/web-sys/latest/web_sys/struct.Event.html) | Unsupported |
| `onemptied` | [Event](https://docs.rs/web-sys/latest/web_sys/struct.Event.html) | Unsupported |
| `onended` | [Event](https://docs.rs/web-sys/latest/web_sys/struct.Event.html) | Unsupported |
| `onerror` | [Event](https://docs.rs/web-sys/latest/web_sys/struct.Event.html) | [ResourceErrorEvent](https://docs.rs/stdweb/latest/stdweb/web/event/struct.ResourceErrorEvent.html) |
| `onfocus` | [FocusEvent](https://docs.rs/web-sys/latest/web_sys/struct.FocusEvent.html) | [FocusEvent](https://docs.rs/stdweb/latest/stdweb/web/event/struct.FocusEvent.html) |
| `onformdata` | [Event](https://docs.rs/web-sys/latest/web_sys/struct.Event.html) | Unsupported |
| `oninput` | [InputData](https://docs.rs/yew/latest/yew/events/struct.InputData.html) | [InputData](https://docs.rs/yew-stdweb/latest/yew_stdweb/events/struct.InputData.html) |
| `oninvalid` | [Event](https://docs.rs/web-sys/latest/web_sys/struct.Event.html) | Unsupported |
| `onkeydown` | [KeyboardEvent](https://docs.rs/web-sys/latest/web_sys/struct.KeyboardEvent.html) | [KeyDownEvent](https://docs.rs/stdweb/latest/stdweb/web/event/struct.KeyDownEvent.html) |
| `onkeypress` | [KeyboardEvent](https://docs.rs/web-sys/latest/web_sys/struct.KeyboardEvent.html) | [KeyPressEvent](https://docs.rs/stdweb/latest/stdweb/web/event/struct.KeyPressEvent.html) |
| `onkeyup` | [KeyboardEvent](https://docs.rs/web-sys/latest/web_sys/struct.KeyboardEvent.html) | [KeyUpEvent](https://docs.rs/stdweb/latest/stdweb/web/event/struct.KeyUpEvent.html) |
| `onload` | [Event](https://docs.rs/web-sys/latest/web_sys/struct.Event.html) | [ResourceLoadEvent](https://docs.rs/stdweb/latest/stdweb/web/event/struct.ResourceLoadEvent.html) |
| `onloadeddata` | [Event](https://docs.rs/web-sys/latest/web_sys/struct.Event.html) | Unsupported |
| `onloadedmetadata` | [Event](https://docs.rs/web-sys/latest/web_sys/struct.Event.html) | Unsupported |
| `onloadstart` | [ProgressEvent](https://docs.rs/web-sys/latest/web_sys/struct.ProgressEvent.html) | [LoadStartEvent](https://docs.rs/stdweb/latest/stdweb/web/event/struct.LoadStartEvent.html) |
| `onmousedown` | [MouseEvent](https://docs.rs/web-sys/latest/web_sys/struct.MouseEvent.html) | [MouseDownEvent](https://docs.rs/stdweb/latest/stdweb/web/event/struct.MouseDownEvent.html) |
| `onmouseenter` | [MouseEvent](https://docs.rs/web-sys/latest/web_sys/struct.MouseEvent.html) | [MouseEnterEvent](https://docs.rs/stdweb/latest/stdweb/web/event/struct.MouseEnterEvent.html) |
| `onmouseleave` | [MouseEvent](https://docs.rs/web-sys/latest/web_sys/struct.MouseEvent.html) | [MouseLeaveEvent](https://docs.rs/stdweb/latest/stdweb/web/event/struct.MouseLeaveEvent.html) |
| `onmousemove` | [MouseEvent](https://docs.rs/web-sys/latest/web_sys/struct.MouseEvent.html) | [MouseMoveEvent](https://docs.rs/stdweb/latest/stdweb/web/event/struct.MouseMoveEvent.html) |
| `onmouseout` | [MouseEvent](https://docs.rs/web-sys/latest/web_sys/struct.MouseEvent.html) | [MouseOutEvent](https://docs.rs/stdweb/latest/stdweb/web/event/struct.MouseOutEvent.html) |
| `onmouseover` | [MouseEvent](https://docs.rs/web-sys/latest/web_sys/struct.MouseEvent.html) | [MouseOverEvent](https://docs.rs/stdweb/latest/stdweb/web/event/struct.MouseOverEvent.html) |
| `onmouseup` | [MouseEvent](https://docs.rs/web-sys/latest/web_sys/struct.MouseEvent.html) | [MouseUpEvent](https://docs.rs/stdweb/latest/stdweb/web/event/struct.MouseUpEvent.html) |
| `onpause` | [Event](https://docs.rs/web-sys/latest/web_sys/struct.Event.html) | Unsupported |
| `onplay` | [Event](https://docs.rs/web-sys/latest/web_sys/struct.Event.html) | Unsupported |
| `onplaying` | [Event](https://docs.rs/web-sys/latest/web_sys/struct.Event.html) | Unsupported |
| `onprogress` | [ProgressEvent](https://docs.rs/web-sys/latest/web_sys/struct.ProgressEvent.html) | [ProgressEvent](https://docs.rs/stdweb/latest/stdweb/web/event/struct.ProgressEvent.html) |
| `onratechange` | [Event](https://docs.rs/web-sys/latest/web_sys/struct.Event.html) | Unsupported |
| `onreset` | [Event](https://docs.rs/web-sys/latest/web_sys/struct.Event.html) | Unsupported |
| `onresize` | [Event](https://docs.rs/web-sys/latest/web_sys/struct.Event.html) | [ResizeEvent](https://docs.rs/stdweb/latest/stdweb/web/event/struct.ResizeEvent.html) |
| `onscroll` | [Event](https://docs.rs/web-sys/latest/web_sys/struct.Event.html) | [ScrollEvent](https://docs.rs/stdweb/latest/stdweb/web/event/struct.ScrollEvent.html) |
| `onsecuritypolicyviolation` | [Event](https://docs.rs/web-sys/latest/web_sys/struct.Event.html) | Unsupported |
| `onseeked` | [Event](https://docs.rs/web-sys/latest/web_sys/struct.Event.html) | Unsupported |
| `onseeking` | [Event](https://docs.rs/web-sys/latest/web_sys/struct.Event.html) | Unsupported |
| `onselect` | [Event](https://docs.rs/web-sys/latest/web_sys/struct.Event.html) | Unsupported |
| `onslotchange` | [Event](https://docs.rs/web-sys/latest/web_sys/struct.Event.html) | [SlotChangeEvent](https://docs.rs/stdweb/latest/stdweb/web/event/struct.SlotChangeEvent.html) |
| `onstalled` | [Event](https://docs.rs/web-sys/latest/web_sys/struct.Event.html) | Unsupported |
| `onsubmit` | [FocusEvent](https://docs.rs/web-sys/latest/web_sys/struct.FocusEvent.html) | [SubmitEvent](https://docs.rs/stdweb/latest/stdweb/web/event/struct.SubmitEvent.html) |
| `onsuspend` | [Event](https://docs.rs/web-sys/latest/web_sys/struct.Event.html) | Unsupported |
| `ontimeupdate` | [Event](https://docs.rs/web-sys/latest/web_sys/struct.Event.html) | Unsupported |
| `ontoggle` | [Event](https://docs.rs/web-sys/latest/web_sys/struct.Event.html) | Unsupported |
| `onvolumechange` | [Event](https://docs.rs/web-sys/latest/web_sys/struct.Event.html) | Unsupported |
| `onwaiting` | [Event](https://docs.rs/web-sys/latest/web_sys/struct.Event.html) | Unsupported |
| `onwheel` | [WheelEvent](https://docs.rs/web-sys/latest/web_sys/struct.WheelEvent.html) | [MouseWheelEvent](https://docs.rs/stdweb/latest/stdweb/web/event/struct.MouseWheelEvent.html) |
| `oncopy` | [Event](https://docs.rs/web-sys/latest/web_sys/struct.Event.html) | Unsupported |
| `oncut` | [Event](https://docs.rs/web-sys/latest/web_sys/struct.Event.html) | Unsupported |
| `onpaste` | [Event](https://docs.rs/web-sys/latest/web_sys/struct.Event.html) | Unsupported |
| `onanimationcancel` | [AnimationEvent](https://docs.rs/web-sys/latest/web_sys/struct.AnimationEvent.html) | Unsupported |
| `onanimationend` | [AnimationEvent](https://docs.rs/web-sys/latest/web_sys/struct.AnimationEvent.html) | Unsupported |
| `onanimationiteration` | [AnimationEvent](https://docs.rs/web-sys/latest/web_sys/struct.AnimationEvent.html) | Unsupported |
| `onanimationstart` | [AnimationEvent](https://docs.rs/web-sys/latest/web_sys/struct.AnimationEvent.html) | Unsupported |
| `ongotpointercapture` | [PointerEvent](https://docs.rs/web-sys/latest/web_sys/struct.PointerEvent.html) | [GotPointerCaptureEvent](https://docs.rs/stdweb/latest/stdweb/web/event/struct.GotPointerCaptureEvent.html) |
| `onloadend` | [ProgressEvent](https://docs.rs/web-sys/latest/web_sys/struct.ProgressEvent.html) | [LoadEndEvent](https://docs.rs/stdweb/latest/stdweb/web/event/struct.LoadEndEvent.html) |
| `onlostpointercapture` | [PointerEvent](https://docs.rs/web-sys/latest/web_sys/struct.PointerEvent.html) | [LostPointerCaptureEvent](https://docs.rs/stdweb/latest/stdweb/web/event/struct.LostPointerCaptureEvent.html) |
| `onpointercancel` | [PointerEvent](https://docs.rs/web-sys/latest/web_sys/struct.PointerEvent.html) | [PointerCancelEvent](https://docs.rs/stdweb/latest/stdweb/web/event/struct.PointerCancelEvent.html) |
| `onpointerdown` | [PointerEvent](https://docs.rs/web-sys/latest/web_sys/struct.PointerEvent.html) | [PointerDownEvent](https://docs.rs/stdweb/latest/stdweb/web/event/struct.PointerDownEvent.html) |
| `onpointerenter` | [PointerEvent](https://docs.rs/web-sys/latest/web_sys/struct.PointerEvent.html) | [PointerEnterEvent](https://docs.rs/stdweb/latest/stdweb/web/event/struct.PointerEnterEvent.html) |
| `onpointerleave` | [PointerEvent](https://docs.rs/web-sys/latest/web_sys/struct.PointerEvent.html) | [PointerLeaveEvent](https://docs.rs/stdweb/latest/stdweb/web/event/struct.PointerLeaveEvent.html) |
| `onpointerlockchange` | [Event](https://docs.rs/web-sys/latest/web_sys/struct.Event.html) | [PointerLockChangeEvent](https://docs.rs/stdweb/latest/stdweb/web/event/struct.PointerLockChangeEvent.html) |
| `onpointerlockerror` | [Event](https://docs.rs/web-sys/latest/web_sys/struct.Event.html) | [PointerLockErrorEvent](https://docs.rs/stdweb/latest/stdweb/web/event/struct.PointerLockErrorEvent.html) |
| `onpointermove` | [PointerEvent](https://docs.rs/web-sys/latest/web_sys/struct.PointerEvent.html) | [PointerMoveEvent](https://docs.rs/stdweb/latest/stdweb/web/event/struct.PointerMoveEvent.html) |
| `onpointerout` | [PointerEvent](https://docs.rs/web-sys/latest/web_sys/struct.PointerEvent.html) | [PointerOutEvent](https://docs.rs/stdweb/latest/stdweb/web/event/struct.PointerOutEvent.html) |
| `onpointerover` | [PointerEvent](https://docs.rs/web-sys/latest/web_sys/struct.PointerEvent.html) | [PointerOverEvent](https://docs.rs/stdweb/latest/stdweb/web/event/struct.PointerOverEvent.html) |
| `onpointerup` | [PointerEvent](https://docs.rs/web-sys/latest/web_sys/struct.PointerEvent.html) | [PointerUpEvent](https://docs.rs/stdweb/latest/stdweb/web/event/struct.PointerUpEvent.html) |
| `onselectionchange` | [Event](https://docs.rs/web-sys/latest/web_sys/struct.Event.html) | [SelectionChangeEvent](https://docs.rs/stdweb/latest/stdweb/web/event/struct.SelectionChangeEvent.html) |
| `onselectstart` | [Event](https://docs.rs/web-sys/latest/web_sys/struct.Event.html) | Unsupported |
| `onshow` | [Event](https://docs.rs/web-sys/latest/web_sys/struct.Event.html) | Unsupported |
| `ontouchcancel` | [TouchEvent](https://docs.rs/web-sys/latest/web_sys/struct.TouchEvent.html) | [TouchCancel](https://docs.rs/stdweb/latest/stdweb/web/event/struct.TouchCancel.html) |
| `ontouchend` | [TouchEvent](https://docs.rs/web-sys/latest/web_sys/struct.TouchEvent.html) | [TouchEnd](https://docs.rs/stdweb/latest/stdweb/web/event/struct.TouchEnd.html) |
| `ontouchmove` | [TouchEvent](https://docs.rs/web-sys/latest/web_sys/struct.TouchEvent.html) | [TouchMove](https://docs.rs/stdweb/latest/stdweb/web/event/struct.TouchMove.html) |
| `ontouchstart` | [TouchEvent](https://docs.rs/web-sys/latest/web_sys/struct.TouchEvent.html) | [TouchStart](https://docs.rs/stdweb/latest/stdweb/web/event/struct.TouchStart.html) |
| `ontransitioncancel` | [TransitionEvent](https://docs.rs/web-sys/latest/web_sys/struct.TransitionEvent.html) | Unsupported |
| `ontransitionend` | [TransitionEvent](https://docs.rs/web-sys/latest/web_sys/struct.TransitionEvent.html) | Unsupported |
| `ontransitionrun` | [TransitionEvent](https://docs.rs/web-sys/latest/web_sys/struct.TransitionEvent.html) | Unsupported |
| `ontransitionstart` | [TransitionEvent](https://docs.rs/web-sys/latest/web_sys/struct.TransitionEvent.html) | Unsupported |
| Event name | `web_sys` Event Type | `stdweb` Event Type |
| --------------------------- | ------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------- |
| `onabort` | [Event](https://docs.rs/web-sys/latest/web_sys/struct.Event.html) | [ResourceAbortEvent](https://docs.rs/stdweb/latest/stdweb/web/event/struct.ResourceAbortEvent.html) |
| `onauxclick` | [MouseEvent](https://docs.rs/web-sys/latest/web_sys/struct.MouseEvent.html) | [AuxClickEvent](https://docs.rs/stdweb/latest/stdweb/web/event/struct.AuxClickEvent.html) |
| `onblur` | [FocusEvent](https://docs.rs/web-sys/latest/web_sys/struct.FocusEvent.html) | [BlurEvent](https://docs.rs/stdweb/latest/stdweb/web/event/struct.BlurEvent.html) |
| `oncancel` | [Event](https://docs.rs/web-sys/latest/web_sys/struct.Event.html) | Unsupported |
| `oncanplay` | [Event](https://docs.rs/web-sys/latest/web_sys/struct.Event.html) | Unsupported |
| `oncanplaythrough` | [Event](https://docs.rs/web-sys/latest/web_sys/struct.Event.html) | Unsupported |
| `onchange` | [ChangeData](https://docs.rs/yew/latest/yew/events/enum.ChangeData.html) | [ChangeData](https://docs.rs/yew-stdweb/latest/yew_stdweb/events/enum.ChangeData.html) |
| `onclick` | [MouseEvent](https://docs.rs/web-sys/latest/web_sys/struct.MouseEvent.html) | [ClickEvent](https://docs.rs/stdweb/latest/stdweb/web/event/struct.ClickEvent.html) |
| `onclose` | [Event](https://docs.rs/web-sys/latest/web_sys/struct.Event.html) | Unsupported |
| `oncontextmenu` | [MouseEvent](https://docs.rs/web-sys/latest/web_sys/struct.MouseEvent.html) | [ContextMenuEvent](https://docs.rs/stdweb/latest/stdweb/web/event/struct.ContextMenuEvent.html) |
| `oncuechange` | [Event](https://docs.rs/web-sys/latest/web_sys/struct.Event.html) | Unsupported |
| `ondblclick` | [MouseEvent](https://docs.rs/web-sys/latest/web_sys/struct.MouseEvent.html) | [DoubleClickEvent](https://docs.rs/stdweb/latest/stdweb/web/event/struct.DoubleClickEvent.html) |
| `ondrag` | [DragEvent](https://docs.rs/web-sys/latest/web_sys/struct.DragEvent.html) | [DragEvent](https://docs.rs/stdweb/latest/stdweb/web/event/struct.DragEvent.html) |
| `ondragend` | [DragEvent](https://docs.rs/web-sys/latest/web_sys/struct.DragEvent.html) | [DragEndEvent](https://docs.rs/stdweb/latest/stdweb/web/event/struct.DragEndEvent.html) |
| `ondragenter` | [DragEvent](https://docs.rs/web-sys/latest/web_sys/struct.DragEvent.html) | [DragEnterEvent](https://docs.rs/stdweb/latest/stdweb/web/event/struct.DragEnterEvent.html) |
| `ondragexit` | [DragEvent](https://docs.rs/web-sys/latest/web_sys/struct.DragEvent.html) | [DragExitEvent](https://docs.rs/stdweb/latest/stdweb/web/event/struct.DragExitEvent.html) |
| `ondragleave` | [DragEvent](https://docs.rs/web-sys/latest/web_sys/struct.DragEvent.htmk) | [DragLeaveEvent](https://docs.rs/stdweb/latest/stdweb/web/event/struct.DragLeaveEvent.html) |
| `ondragover` | [DragEvent](https://docs.rs/web-sys/latest/web_sys/struct.DragEvent.html) | [DragOverEvent](https://docs.rs/stdweb/latest/stdweb/web/event/struct.DragOverEvent.html) |
| `ondragstart` | [DragEvent](https://docs.rs/web-sys/latest/web_sys/struct.DragEvent.html) | [DragStartEvent](https://docs.rs/stdweb/latest/stdweb/web/event/struct.DragStartEvent.html) |
| `ondrop` | [DragEvent](https://docs.rs/web-sys/latest/web_sys/struct.DragEvent.html) | [DragDropEvent](https://docs.rs/stdweb/latest/stdweb/web/event/struct.DragDropEvent.html) |
| `ondurationchange` | [Event](https://docs.rs/web-sys/latest/web_sys/struct.Event.html) | Unsupported |
| `onemptied` | [Event](https://docs.rs/web-sys/latest/web_sys/struct.Event.html) | Unsupported |
| `onended` | [Event](https://docs.rs/web-sys/latest/web_sys/struct.Event.html) | Unsupported |
| `onerror` | [Event](https://docs.rs/web-sys/latest/web_sys/struct.Event.html) | [ResourceErrorEvent](https://docs.rs/stdweb/latest/stdweb/web/event/struct.ResourceErrorEvent.html) |
| `onfocus` | [FocusEvent](https://docs.rs/web-sys/latest/web_sys/struct.FocusEvent.html) | [FocusEvent](https://docs.rs/stdweb/latest/stdweb/web/event/struct.FocusEvent.html) |
| `onformdata` | [Event](https://docs.rs/web-sys/latest/web_sys/struct.Event.html) | Unsupported |
| `oninput` | [InputData](https://docs.rs/yew/latest/yew/events/struct.InputData.html) | [InputData](https://docs.rs/yew-stdweb/latest/yew_stdweb/events/struct.InputData.html) |
| `oninvalid` | [Event](https://docs.rs/web-sys/latest/web_sys/struct.Event.html) | Unsupported |
| `onkeydown` | [KeyboardEvent](https://docs.rs/web-sys/latest/web_sys/struct.KeyboardEvent.html) | [KeyDownEvent](https://docs.rs/stdweb/latest/stdweb/web/event/struct.KeyDownEvent.html) |
| `onkeypress` | [KeyboardEvent](https://docs.rs/web-sys/latest/web_sys/struct.KeyboardEvent.html) | [KeyPressEvent](https://docs.rs/stdweb/latest/stdweb/web/event/struct.KeyPressEvent.html) |
| `onkeyup` | [KeyboardEvent](https://docs.rs/web-sys/latest/web_sys/struct.KeyboardEvent.html) | [KeyUpEvent](https://docs.rs/stdweb/latest/stdweb/web/event/struct.KeyUpEvent.html) |
| `onload` | [Event](https://docs.rs/web-sys/latest/web_sys/struct.Event.html) | [ResourceLoadEvent](https://docs.rs/stdweb/latest/stdweb/web/event/struct.ResourceLoadEvent.html) |
| `onloadeddata` | [Event](https://docs.rs/web-sys/latest/web_sys/struct.Event.html) | Unsupported |
| `onloadedmetadata` | [Event](https://docs.rs/web-sys/latest/web_sys/struct.Event.html) | Unsupported |
| `onloadstart` | [ProgressEvent](https://docs.rs/web-sys/latest/web_sys/struct.ProgressEvent.html) | [LoadStartEvent](https://docs.rs/stdweb/latest/stdweb/web/event/struct.LoadStartEvent.html) |
| `onmousedown` | [MouseEvent](https://docs.rs/web-sys/latest/web_sys/struct.MouseEvent.html) | [MouseDownEvent](https://docs.rs/stdweb/latest/stdweb/web/event/struct.MouseDownEvent.html) |
| `onmouseenter` | [MouseEvent](https://docs.rs/web-sys/latest/web_sys/struct.MouseEvent.html) | [MouseEnterEvent](https://docs.rs/stdweb/latest/stdweb/web/event/struct.MouseEnterEvent.html) |
| `onmouseleave` | [MouseEvent](https://docs.rs/web-sys/latest/web_sys/struct.MouseEvent.html) | [MouseLeaveEvent](https://docs.rs/stdweb/latest/stdweb/web/event/struct.MouseLeaveEvent.html) |
| `onmousemove` | [MouseEvent](https://docs.rs/web-sys/latest/web_sys/struct.MouseEvent.html) | [MouseMoveEvent](https://docs.rs/stdweb/latest/stdweb/web/event/struct.MouseMoveEvent.html) |
| `onmouseout` | [MouseEvent](https://docs.rs/web-sys/latest/web_sys/struct.MouseEvent.html) | [MouseOutEvent](https://docs.rs/stdweb/latest/stdweb/web/event/struct.MouseOutEvent.html) |
| `onmouseover` | [MouseEvent](https://docs.rs/web-sys/latest/web_sys/struct.MouseEvent.html) | [MouseOverEvent](https://docs.rs/stdweb/latest/stdweb/web/event/struct.MouseOverEvent.html) |
| `onmouseup` | [MouseEvent](https://docs.rs/web-sys/latest/web_sys/struct.MouseEvent.html) | [MouseUpEvent](https://docs.rs/stdweb/latest/stdweb/web/event/struct.MouseUpEvent.html) |
| `onpause` | [Event](https://docs.rs/web-sys/latest/web_sys/struct.Event.html) | Unsupported |
| `onplay` | [Event](https://docs.rs/web-sys/latest/web_sys/struct.Event.html) | Unsupported |
| `onplaying` | [Event](https://docs.rs/web-sys/latest/web_sys/struct.Event.html) | Unsupported |
| `onprogress` | [ProgressEvent](https://docs.rs/web-sys/latest/web_sys/struct.ProgressEvent.html) | [ProgressEvent](https://docs.rs/stdweb/latest/stdweb/web/event/struct.ProgressEvent.html) |
| `onratechange` | [Event](https://docs.rs/web-sys/latest/web_sys/struct.Event.html) | Unsupported |
| `onreset` | [Event](https://docs.rs/web-sys/latest/web_sys/struct.Event.html) | Unsupported |
| `onresize` | [Event](https://docs.rs/web-sys/latest/web_sys/struct.Event.html) | [ResizeEvent](https://docs.rs/stdweb/latest/stdweb/web/event/struct.ResizeEvent.html) |
| `onscroll` | [Event](https://docs.rs/web-sys/latest/web_sys/struct.Event.html) | [ScrollEvent](https://docs.rs/stdweb/latest/stdweb/web/event/struct.ScrollEvent.html) |
| `onsecuritypolicyviolation` | [Event](https://docs.rs/web-sys/latest/web_sys/struct.Event.html) | Unsupported |
| `onseeked` | [Event](https://docs.rs/web-sys/latest/web_sys/struct.Event.html) | Unsupported |
| `onseeking` | [Event](https://docs.rs/web-sys/latest/web_sys/struct.Event.html) | Unsupported |
| `onselect` | [Event](https://docs.rs/web-sys/latest/web_sys/struct.Event.html) | Unsupported |
| `onslotchange` | [Event](https://docs.rs/web-sys/latest/web_sys/struct.Event.html) | [SlotChangeEvent](https://docs.rs/stdweb/latest/stdweb/web/event/struct.SlotChangeEvent.html) |
| `onstalled` | [Event](https://docs.rs/web-sys/latest/web_sys/struct.Event.html) | Unsupported |
| `onsubmit` | [FocusEvent](https://docs.rs/web-sys/latest/web_sys/struct.FocusEvent.html) | [SubmitEvent](https://docs.rs/stdweb/latest/stdweb/web/event/struct.SubmitEvent.html) |
| `onsuspend` | [Event](https://docs.rs/web-sys/latest/web_sys/struct.Event.html) | Unsupported |
| `ontimeupdate` | [Event](https://docs.rs/web-sys/latest/web_sys/struct.Event.html) | Unsupported |
| `ontoggle` | [Event](https://docs.rs/web-sys/latest/web_sys/struct.Event.html) | Unsupported |
| `onvolumechange` | [Event](https://docs.rs/web-sys/latest/web_sys/struct.Event.html) | Unsupported |
| `onwaiting` | [Event](https://docs.rs/web-sys/latest/web_sys/struct.Event.html) | Unsupported |
| `onwheel` | [WheelEvent](https://docs.rs/web-sys/latest/web_sys/struct.WheelEvent.html) | [MouseWheelEvent](https://docs.rs/stdweb/latest/stdweb/web/event/struct.MouseWheelEvent.html) |
| `oncopy` | [Event](https://docs.rs/web-sys/latest/web_sys/struct.Event.html) | Unsupported |
| `oncut` | [Event](https://docs.rs/web-sys/latest/web_sys/struct.Event.html) | Unsupported |
| `onpaste` | [Event](https://docs.rs/web-sys/latest/web_sys/struct.Event.html) | Unsupported |
| `onanimationcancel` | [AnimationEvent](https://docs.rs/web-sys/latest/web_sys/struct.AnimationEvent.html) | Unsupported |
| `onanimationend` | [AnimationEvent](https://docs.rs/web-sys/latest/web_sys/struct.AnimationEvent.html) | Unsupported |
| `onanimationiteration` | [AnimationEvent](https://docs.rs/web-sys/latest/web_sys/struct.AnimationEvent.html) | Unsupported |
| `onanimationstart` | [AnimationEvent](https://docs.rs/web-sys/latest/web_sys/struct.AnimationEvent.html) | Unsupported |
| `ongotpointercapture` | [PointerEvent](https://docs.rs/web-sys/latest/web_sys/struct.PointerEvent.html) | [GotPointerCaptureEvent](https://docs.rs/stdweb/latest/stdweb/web/event/struct.GotPointerCaptureEvent.html) |
| `onloadend` | [ProgressEvent](https://docs.rs/web-sys/latest/web_sys/struct.ProgressEvent.html) | [LoadEndEvent](https://docs.rs/stdweb/latest/stdweb/web/event/struct.LoadEndEvent.html) |
| `onlostpointercapture` | [PointerEvent](https://docs.rs/web-sys/latest/web_sys/struct.PointerEvent.html) | [LostPointerCaptureEvent](https://docs.rs/stdweb/latest/stdweb/web/event/struct.LostPointerCaptureEvent.html) |
| `onpointercancel` | [PointerEvent](https://docs.rs/web-sys/latest/web_sys/struct.PointerEvent.html) | [PointerCancelEvent](https://docs.rs/stdweb/latest/stdweb/web/event/struct.PointerCancelEvent.html) |
| `onpointerdown` | [PointerEvent](https://docs.rs/web-sys/latest/web_sys/struct.PointerEvent.html) | [PointerDownEvent](https://docs.rs/stdweb/latest/stdweb/web/event/struct.PointerDownEvent.html) |
| `onpointerenter` | [PointerEvent](https://docs.rs/web-sys/latest/web_sys/struct.PointerEvent.html) | [PointerEnterEvent](https://docs.rs/stdweb/latest/stdweb/web/event/struct.PointerEnterEvent.html) |
| `onpointerleave` | [PointerEvent](https://docs.rs/web-sys/latest/web_sys/struct.PointerEvent.html) | [PointerLeaveEvent](https://docs.rs/stdweb/latest/stdweb/web/event/struct.PointerLeaveEvent.html) |
| `onpointerlockchange` | [Event](https://docs.rs/web-sys/latest/web_sys/struct.Event.html) | [PointerLockChangeEvent](https://docs.rs/stdweb/latest/stdweb/web/event/struct.PointerLockChangeEvent.html) |
| `onpointerlockerror` | [Event](https://docs.rs/web-sys/latest/web_sys/struct.Event.html) | [PointerLockErrorEvent](https://docs.rs/stdweb/latest/stdweb/web/event/struct.PointerLockErrorEvent.html) |
| `onpointermove` | [PointerEvent](https://docs.rs/web-sys/latest/web_sys/struct.PointerEvent.html) | [PointerMoveEvent](https://docs.rs/stdweb/latest/stdweb/web/event/struct.PointerMoveEvent.html) |
| `onpointerout` | [PointerEvent](https://docs.rs/web-sys/latest/web_sys/struct.PointerEvent.html) | [PointerOutEvent](https://docs.rs/stdweb/latest/stdweb/web/event/struct.PointerOutEvent.html) |
| `onpointerover` | [PointerEvent](https://docs.rs/web-sys/latest/web_sys/struct.PointerEvent.html) | [PointerOverEvent](https://docs.rs/stdweb/latest/stdweb/web/event/struct.PointerOverEvent.html) |
| `onpointerup` | [PointerEvent](https://docs.rs/web-sys/latest/web_sys/struct.PointerEvent.html) | [PointerUpEvent](https://docs.rs/stdweb/latest/stdweb/web/event/struct.PointerUpEvent.html) |
| `onselectionchange` | [Event](https://docs.rs/web-sys/latest/web_sys/struct.Event.html) | [SelectionChangeEvent](https://docs.rs/stdweb/latest/stdweb/web/event/struct.SelectionChangeEvent.html) |
| `onselectstart` | [Event](https://docs.rs/web-sys/latest/web_sys/struct.Event.html) | Unsupported |
| `onshow` | [Event](https://docs.rs/web-sys/latest/web_sys/struct.Event.html) | Unsupported |
| `ontouchcancel` | [TouchEvent](https://docs.rs/web-sys/latest/web_sys/struct.TouchEvent.html) | [TouchCancel](https://docs.rs/stdweb/latest/stdweb/web/event/struct.TouchCancel.html) |
| `ontouchend` | [TouchEvent](https://docs.rs/web-sys/latest/web_sys/struct.TouchEvent.html) | [TouchEnd](https://docs.rs/stdweb/latest/stdweb/web/event/struct.TouchEnd.html) |
| `ontouchmove` | [TouchEvent](https://docs.rs/web-sys/latest/web_sys/struct.TouchEvent.html) | [TouchMove](https://docs.rs/stdweb/latest/stdweb/web/event/struct.TouchMove.html) |
| `ontouchstart` | [TouchEvent](https://docs.rs/web-sys/latest/web_sys/struct.TouchEvent.html) | [TouchStart](https://docs.rs/stdweb/latest/stdweb/web/event/struct.TouchStart.html) |
| `ontransitioncancel` | [TransitionEvent](https://docs.rs/web-sys/latest/web_sys/struct.TransitionEvent.html) | Unsupported |
| `ontransitionend` | [TransitionEvent](https://docs.rs/web-sys/latest/web_sys/struct.TransitionEvent.html) | Unsupported |
| `ontransitionrun` | [TransitionEvent](https://docs.rs/web-sys/latest/web_sys/struct.TransitionEvent.html) | Unsupported |
| `ontransitionstart` | [TransitionEvent](https://docs.rs/web-sys/latest/web_sys/struct.TransitionEvent.html) | Unsupported |

View File

@ -49,7 +49,7 @@ This was the best preferred tool to use before the creation of `wasm-bindgen`.
| | `trunk` | `wasm-pack` | `cargo-web` |
| ----------------------------- | ---------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------ |
| Project Status | Actively maintained | Actively maintained by the [Rust / Wasm Working Group](https://rustwasm.github.io) | No Github activity for over 6 months |
| Dev Experience | Just works! Batteries included, no external dependencies needed. | Barebones. You'll need to write some scripts to streamline the experience or use the webpack plugin. | Works great for code but needs separate asset pipeline. |
| Dev Experience | Just works! Batteries included, no external dependencies needed. | Bare-bones. You'll need to write some scripts to streamline the experience or use the webpack plugin. | Works great for code but needs separate asset pipeline. |
| Local Server | Supported | Only with webpack plugin | Supported |
| Auto rebuild on local changes | Supported | Only with webpack plugin | Supported |
| Asset handling | Supported | Only with webpack plugin | Static assets only |

View File

@ -3,7 +3,7 @@
use web_sys::HtmlSelectElement;
use yew::callback::Callback;
use yew::html::{ChangeData, Component, ComponentLink, Html, NodeRef, ShouldRender};
use yew::macros::{html, Properties};
use yew::{html, Properties};
/// An alternative to the HTML `<select>` tag.
///

View File

@ -1,13 +1,10 @@
use super::html_iterable::HtmlIterable;
use super::html_node::HtmlNode;
use super::ToNodeIterator;
use super::{HtmlIterable, HtmlNode, ToNodeIterator};
use crate::PeekValue;
use proc_macro2::Delimiter;
use quote::{quote, quote_spanned, ToTokens};
use syn::braced;
use syn::buffer::Cursor;
use syn::parse::{Parse, ParseStream, Result as ParseResult};
use syn::token;
use syn::parse::{Parse, ParseStream};
use syn::{braced, token};
pub struct HtmlBlock {
content: BlockContent,
@ -26,7 +23,7 @@ impl PeekValue<()> for HtmlBlock {
}
impl Parse for HtmlBlock {
fn parse(input: ParseStream) -> ParseResult<Self> {
fn parse(input: ParseStream) -> syn::Result<Self> {
let content;
let brace = braced!(content in input);
let content = if HtmlIterable::peek(content.cursor()).is_some() {

View File

@ -1,23 +1,20 @@
use super::HtmlChildrenTree;
use super::HtmlProp;
use super::HtmlPropSuffix;
use crate::PeekValue;
use super::{HtmlChildrenTree, TagTokens};
use crate::{props::ComponentProps, PeekValue};
use boolinator::Boolinator;
use proc_macro2::Span;
use quote::{quote, quote_spanned, ToTokens};
use std::cmp::Ordering;
use syn::buffer::Cursor;
use syn::parse::{Parse, ParseStream, Result as ParseResult};
use syn::parse::{Parse, ParseStream};
use syn::punctuated::Punctuated;
use syn::spanned::Spanned;
use syn::{
AngleBracketedGenericArguments, Expr, GenericArgument, Ident, Path, PathArguments, PathSegment,
Token, Type, TypePath,
AngleBracketedGenericArguments, GenericArgument, Path, PathArguments, PathSegment, Token, Type,
TypePath,
};
pub struct HtmlComponent {
ty: Type,
props: Props,
props: ComponentProps,
children: HtmlChildrenTree,
}
@ -30,11 +27,11 @@ impl PeekValue<()> for HtmlComponent {
}
impl Parse for HtmlComponent {
fn parse(input: ParseStream) -> ParseResult<Self> {
fn parse(input: ParseStream) -> syn::Result<Self> {
if HtmlComponentClose::peek(input.cursor()).is_some() {
return match input.parse::<HtmlComponentClose>() {
Ok(close) => Err(syn::Error::new_spanned(
close,
close.to_spanned(),
"this closing tag has no corresponding opening tag",
)),
Err(err) => Err(err),
@ -43,7 +40,7 @@ impl Parse for HtmlComponent {
let open = input.parse::<HtmlComponentOpen>()?;
// Return early if it's a self-closing tag
if open.div.is_some() {
if open.is_self_closing() {
return Ok(HtmlComponent {
ty: open.ty,
props: open.props,
@ -55,7 +52,7 @@ impl Parse for HtmlComponent {
loop {
if input.is_empty() {
return Err(syn::Error::new_spanned(
open,
open.to_spanned(),
"this opening tag has no corresponding closing tag",
));
}
@ -70,6 +67,18 @@ impl Parse for HtmlComponent {
input.parse::<HtmlComponentClose>()?;
if !children.is_empty() {
// check if the `children` prop is given explicitly
if let ComponentProps::List(props) = &open.props {
if let Some(children_prop) = props.get_by_label("children") {
return Err(syn::Error::new_spanned(
&children_prop.label,
"cannot specify the `children` prop when the component already has children",
));
}
}
}
Ok(HtmlComponent {
ty: open.ty,
props: open.props,
@ -86,90 +95,36 @@ impl ToTokens for HtmlComponent {
children,
} = self;
let validate_props = if let PropType::List(list_props) = &props.prop_type {
let check_props = list_props.iter().map(|HtmlProp { label, .. }| {
quote! { props.#label; }
});
let check_children = if !children.is_empty() {
quote! { props.children; }
} else {
quote! {}
};
quote! {
let _ = |props: <#ty as ::yew::html::Component>::Properties| {
#check_children
#(#check_props)*
};
}
let props_ty = quote_spanned!(ty.span()=> <#ty as ::yew::html::Component>::Properties);
let children_renderer = if children.is_empty() {
None
} else {
quote! {}
Some(quote! { ::yew::html::ChildrenRenderer::new(#children) })
};
let build_props = props.build_properties_tokens(&props_ty, children_renderer);
let set_children = if !children.is_empty() {
// using span of type because the error message goes something like "children method not found".
// If we could control the message, it should say "component X doesn't accept children" and point at the children.
quote_spanned! {ty.span()=>
.children(::yew::html::ChildrenRenderer::new(#children))
}
} else {
quote! {}
};
let init_props = match &props.prop_type {
PropType::List(list_props) => {
let set_props = list_props.iter().map(|HtmlProp { label, value, .. }| {
quote_spanned! {value.span()=> .#label(
#[allow(unused_braces)]
<::yew::virtual_dom::VComp as ::yew::virtual_dom::Transformer<_, _>>::transform(
#value
)
)}
});
quote_spanned! {ty.span()=>
<<#ty as ::yew::html::Component>::Properties as ::yew::html::Properties>::builder()
#(#set_props)*
#set_children
.build()
}
}
PropType::With(props) => {
quote! { #props }
}
PropType::None => quote_spanned! {ty.span()=>
<<#ty as ::yew::html::Component>::Properties as ::yew::html::Properties>::builder()
#set_children
.build()
},
};
let node_ref = if let Some(node_ref) = &props.node_ref {
quote_spanned! {node_ref.span()=> #node_ref }
let special_props = props.special();
let node_ref = if let Some(node_ref) = &special_props.node_ref {
let value = &node_ref.value;
quote_spanned! {value.span()=> #value }
} else {
quote! { ::yew::html::NodeRef::default() }
};
let key = if let Some(key) = &props.key {
quote_spanned! {key.span()=>
let key = if let Some(key) = &special_props.key {
let value = &key.value;
quote_spanned! {value.span()=>
#[allow(clippy::useless_conversion)]
Some(::std::convert::Into::<::yew::virtual_dom::Key>::into(#key))
Some(::std::convert::Into::<::yew::virtual_dom::Key>::into(#value))
}
} else {
quote! {None}
quote! { None }
};
tokens.extend(quote_spanned! {ty.span()=>
{
// These validation checks show a nice error message to the user.
// They do not execute at runtime
if false {
#validate_props
}
#[allow(clippy::unit_arg)]
::yew::virtual_dom::VChild::<#ty>::new(#init_props, #node_ref, #key)
::yew::virtual_dom::VChild::<#ty>::new(#build_props, #node_ref, #key)
}
});
}
@ -260,11 +215,18 @@ impl HtmlComponent {
}
struct HtmlComponentOpen {
lt: Token![<],
tag: TagTokens,
ty: Type,
props: Props,
div: Option<Token![/]>,
gt: Token![>],
props: ComponentProps,
}
impl HtmlComponentOpen {
fn is_self_closing(&self) -> bool {
self.tag.div.is_some()
}
fn to_spanned(&self) -> impl ToTokens {
self.tag.to_spanned()
}
}
impl PeekValue<Type> for HtmlComponentOpen {
@ -277,36 +239,24 @@ impl PeekValue<Type> for HtmlComponentOpen {
}
impl Parse for HtmlComponentOpen {
fn parse(input: ParseStream) -> ParseResult<Self> {
let lt = input.parse::<Token![<]>()?;
let ty = input.parse()?;
// backwards compat
let _ = input.parse::<Token![:]>();
let HtmlPropSuffix { stream, div, gt } = input.parse()?;
let props = syn::parse2(stream)?;
fn parse(input: ParseStream) -> syn::Result<Self> {
TagTokens::parse_start_content(input, |input, tag| {
let ty = input.parse()?;
let props = input.parse()?;
Ok(HtmlComponentOpen {
lt,
ty,
props,
div,
gt,
Ok(Self { tag, ty, props })
})
}
}
impl ToTokens for HtmlComponentOpen {
fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) {
let HtmlComponentOpen { lt, gt, .. } = self;
tokens.extend(quote! {#lt#gt});
}
}
struct HtmlComponentClose {
lt: Token![<],
div: Token![/],
ty: Type,
gt: Token![>],
tag: TagTokens,
_ty: Type,
}
impl HtmlComponentClose {
fn to_spanned(&self) -> impl ToTokens {
self.tag.to_spanned()
}
}
impl PeekValue<Type> for HtmlComponentClose {
@ -326,130 +276,10 @@ impl PeekValue<Type> for HtmlComponentClose {
}
}
impl Parse for HtmlComponentClose {
fn parse(input: ParseStream) -> ParseResult<Self> {
Ok(HtmlComponentClose {
lt: input.parse()?,
div: input.parse()?,
ty: input.parse()?,
gt: input.parse()?,
fn parse(input: ParseStream) -> syn::Result<Self> {
TagTokens::parse_end_content(input, |input, tag| {
let ty = input.parse()?;
Ok(Self { tag, _ty: ty })
})
}
}
impl ToTokens for HtmlComponentClose {
fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) {
let HtmlComponentClose { lt, div, ty, gt } = self;
tokens.extend(quote! {#lt#div#ty#gt});
}
}
enum PropType {
List(Vec<HtmlProp>),
With(Ident),
None,
}
struct Props {
node_ref: Option<Expr>,
key: Option<Expr>,
prop_type: PropType,
}
const COLLISION_MSG: &str = "Using the `with props` syntax in combination with named props is not allowed (note: this does not apply to the `ref` prop).";
impl Parse for Props {
fn parse(input: ParseStream) -> ParseResult<Self> {
let mut props = Props {
node_ref: None,
key: None,
prop_type: PropType::None,
};
while let Some((token, _)) = input.cursor().ident() {
if token == "with" {
match props.prop_type {
PropType::None => Ok(()),
PropType::With(_) => Err(input.error("too many `with` tokens used")),
PropType::List(_) => Err(syn::Error::new_spanned(&token, COLLISION_MSG)),
}?;
input.parse::<Ident>()?;
props.prop_type = PropType::With(input.parse::<Ident>().map_err(|_| {
syn::Error::new_spanned(&token, "`with` must be followed by an identifier")
})?);
// Handle optional comma
let _ = input.parse::<Token![,]>();
continue;
}
if (HtmlProp::peek(input.cursor())).is_none() {
break;
}
let prop = input.parse::<HtmlProp>()?;
if prop.label.to_string() == "ref" {
match props.node_ref {
None => Ok(()),
Some(_) => Err(syn::Error::new_spanned(&prop.label, "too many refs set")),
}?;
props.node_ref = Some(prop.value);
continue;
}
if prop.label.to_string() == "key" {
match props.key {
None => Ok(()),
Some(_) => Err(syn::Error::new_spanned(&prop.label, "too many keys set")),
}?;
props.key = Some(prop.value);
continue;
}
if prop.label.to_string() == "type" {
return Err(syn::Error::new_spanned(&prop.label, "expected identifier"));
}
if !prop.label.extended.is_empty() {
return Err(syn::Error::new_spanned(&prop.label, "expected identifier"));
}
if prop.question_mark.is_some() {
return Err(syn::Error::new_spanned(
&prop.label,
"optional attributes are only supported on HTML tags. Yew components can use `Option<T>` properties to accomplish the same thing.",
));
}
match props.prop_type {
ref mut prop_type @ PropType::None => {
*prop_type = PropType::List(vec![prop]);
}
PropType::With(_) => return Err(syn::Error::new_spanned(&token, COLLISION_MSG)),
PropType::List(ref mut list) => {
list.push(prop);
}
};
}
if let PropType::List(list) = &mut props.prop_type {
// sort alphabetically
list.sort_by(|a, b| {
if a.label == b.label {
Ordering::Equal
} else if a.label.to_string() == "children" {
Ordering::Greater
} else if b.label.to_string() == "children" {
Ordering::Less
} else {
a.label
.to_string()
.partial_cmp(&b.label.to_string())
.unwrap()
}
});
}
Ok(props)
}
}

View File

@ -6,7 +6,7 @@ use quote::{quote, ToTokens};
use std::fmt;
use syn::buffer::Cursor;
use syn::ext::IdentExt;
use syn::parse::{Parse, ParseStream, Result as ParseResult};
use syn::parse::{Parse, ParseStream};
use syn::{spanned::Spanned, LitStr, Token};
#[derive(Clone, PartialEq)]
@ -16,13 +16,6 @@ pub struct HtmlDashedName {
}
impl HtmlDashedName {
pub fn new(name: Ident) -> Self {
HtmlDashedName {
name,
extended: Vec::new(),
}
}
pub fn to_ascii_lowercase_string(&self) -> String {
let mut s = self.to_string();
s.make_ascii_lowercase();
@ -68,7 +61,7 @@ impl Peek<'_, Self> for HtmlDashedName {
}
impl Parse for HtmlDashedName {
fn parse(input: ParseStream) -> ParseResult<Self> {
fn parse(input: ParseStream) -> syn::Result<Self> {
let name = input.call(Ident::parse_any)?;
let mut extended = Vec::new();
while input.peek(Token![-]) {

View File

@ -1,56 +1,52 @@
mod tag_attributes;
use super::{
HtmlChildrenTree, HtmlDashedName, HtmlProp as TagAttribute, HtmlPropSuffix as TagSuffix,
};
use super::{HtmlChildrenTree, HtmlDashedName, TagTokens};
use crate::props::{ClassesForm, ElementProps, Prop};
use crate::stringify::Stringify;
use crate::{non_capitalized_ascii, stringify, Peek, PeekValue};
use boolinator::Boolinator;
use proc_macro2::{Delimiter, Span, TokenStream};
use proc_macro2::{Delimiter, TokenStream};
use quote::{quote, quote_spanned, ToTokens};
use syn::buffer::Cursor;
use syn::parse::{Parse, ParseStream, Result as ParseResult};
use syn::parse::{Parse, ParseStream};
use syn::spanned::Spanned;
use syn::{Block, Ident, Token};
use tag_attributes::{ClassesForm, TagAttributes};
pub struct HtmlTag {
tag_name: TagName,
attributes: TagAttributes,
pub struct HtmlElement {
name: TagName,
props: ElementProps,
children: HtmlChildrenTree,
}
impl PeekValue<()> for HtmlTag {
impl PeekValue<()> for HtmlElement {
fn peek(cursor: Cursor) -> Option<()> {
HtmlTagOpen::peek(cursor)
.or_else(|| HtmlTagClose::peek(cursor))
HtmlElementOpen::peek(cursor)
.or_else(|| HtmlElementClose::peek(cursor))
.map(|_| ())
}
}
impl Parse for HtmlTag {
fn parse(input: ParseStream) -> ParseResult<Self> {
if HtmlTagClose::peek(input.cursor()).is_some() {
return match input.parse::<HtmlTagClose>() {
impl Parse for HtmlElement {
fn parse(input: ParseStream) -> syn::Result<Self> {
if HtmlElementClose::peek(input.cursor()).is_some() {
return match input.parse::<HtmlElementClose>() {
Ok(close) => Err(syn::Error::new_spanned(
close,
close.to_spanned(),
"this closing tag has no corresponding opening tag",
)),
Err(err) => Err(err),
};
}
let open = input.parse::<HtmlTagOpen>()?;
let open = input.parse::<HtmlElementOpen>()?;
// Return early if it's a self-closing tag
if open.div.is_some() {
return Ok(HtmlTag {
tag_name: open.tag_name,
attributes: open.attributes,
if open.is_self_closing() {
return Ok(HtmlElement {
name: open.name,
props: open.props,
children: HtmlChildrenTree::new(),
});
}
if let TagName::Lit(name) = &open.tag_name {
if let TagName::Lit(name) = &open.name {
// Void elements should not have children.
// See https://html.spec.whatwg.org/multipage/syntax.html#void-elements
//
@ -58,22 +54,22 @@ impl Parse for HtmlTag {
match name.to_ascii_lowercase_string().as_str() {
"area" | "base" | "br" | "col" | "embed" | "hr" | "img" | "input" | "link"
| "meta" | "param" | "source" | "track" | "wbr" => {
return Err(syn::Error::new_spanned(&open, format!("the tag `<{}>` is a void element and cannot have children (hint: rewrite this as `<{0}/>`)", name)));
return Err(syn::Error::new_spanned(open.to_spanned(), format!("the tag `<{}>` is a void element and cannot have children (hint: rewrite this as `<{0}/>`)", name)));
}
_ => {}
}
}
let open_key = open.tag_name.get_key();
let open_key = open.name.get_key();
let mut children = HtmlChildrenTree::new();
loop {
if input.is_empty() {
return Err(syn::Error::new_spanned(
open,
open.to_spanned(),
"this opening tag has no corresponding closing tag",
));
}
if let Some(close_key) = HtmlTagClose::peek(input.cursor()) {
if let Some(close_key) = HtmlElementClose::peek(input.cursor()) {
if open_key == close_key {
break;
}
@ -82,26 +78,26 @@ impl Parse for HtmlTag {
children.parse_child(input)?;
}
input.parse::<HtmlTagClose>()?;
input.parse::<HtmlElementClose>()?;
Ok(HtmlTag {
tag_name: open.tag_name,
attributes: open.attributes,
Ok(Self {
name: open.name,
props: open.props,
children,
})
}
}
impl ToTokens for HtmlTag {
impl ToTokens for HtmlElement {
#[allow(clippy::cognitive_complexity)]
fn to_tokens(&self, tokens: &mut TokenStream) {
let Self {
tag_name,
attributes,
name,
props,
children,
} = self;
let name = match &tag_name {
let name_sr = match &name {
TagName::Lit(name) => name.stringify(),
TagName::Expr(name) => {
let expr = &name.expr;
@ -120,7 +116,7 @@ impl ToTokens for HtmlTag {
}
};
let TagAttributes {
let ElementProps {
classes,
attributes,
booleans,
@ -130,20 +126,22 @@ impl ToTokens for HtmlTag {
node_ref,
key,
listeners,
} = &attributes;
} = &props;
let vtag = Ident::new("__yew_vtag", tag_name.span());
let vtag = Ident::new("__yew_vtag", name.span());
// attributes with special treatment
let set_node_ref = node_ref.as_ref().map(|node_ref| {
let set_node_ref = node_ref.as_ref().map(|attr| {
let value = &attr.value;
quote! {
#vtag.node_ref = #node_ref;
#vtag.node_ref = #value;
}
});
let set_key = key.as_ref().map(|key| {
let set_key = key.as_ref().map(|attr| {
let value = &attr.value;
quote! {
#vtag.key = ::std::option::Option::Some(::std::convert::Into::<::yew::virtual_dom::Key>::into(#key));
#vtag.key = ::std::option::Option::Some(::std::convert::Into::<::yew::virtual_dom::Key>::into(#value));
}
});
let set_value = value.as_ref().map(|attr| {
@ -176,7 +174,8 @@ impl ToTokens for HtmlTag {
}
}
});
let set_checked = checked.as_ref().map(|value| {
let set_checked = checked.as_ref().map(|attr| {
let value = &attr.value;
quote_spanned! {value.span()=>
#vtag.set_checked(#value);
}
@ -188,10 +187,11 @@ impl ToTokens for HtmlTag {
None
} else {
let attrs = attributes.iter().map(
|TagAttribute {
|Prop {
label,
question_mark,
value,
..
}| {
let key = label.to_lit_str();
if question_mark.is_some() {
@ -217,7 +217,7 @@ impl ToTokens for HtmlTag {
} else {
let tokens = booleans
.iter()
.map(|TagAttribute { label, value, .. }| {
.map(|Prop { label, value, .. }| {
let label_str = label.to_lit_str();
let sr = label.stringify();
quote_spanned! {value.span()=> {
@ -279,10 +279,11 @@ impl ToTokens for HtmlTag {
let add_listeners = listeners
.iter()
.map(
|TagAttribute {
|Prop {
label,
question_mark,
value,
..
}| {
let name = &label.name;
@ -311,7 +312,7 @@ impl ToTokens for HtmlTag {
} else {
let listeners_it = listeners
.iter()
.map(|TagAttribute { label, value, .. }| to_wrapped_listener(&label.name, value));
.map(|Prop { label, value, .. }| to_wrapped_listener(&label.name, value));
Some(quote! {
#vtag.add_listeners(::std::vec![#(#listeners_it),*]);
@ -329,7 +330,7 @@ impl ToTokens for HtmlTag {
// These are the runtime-checks exclusive to dynamic tags.
// For literal tags this is already done at compile-time.
let dyn_tag_runtime_checks = if matches!(&tag_name, TagName::Expr(_)) {
let dyn_tag_runtime_checks = if matches!(&name, TagName::Expr(_)) {
// when Span::source_file Span::start get stabilised or yew-macro introduces a nightly feature flag
// we should expand the panic message to contain the exact location of the dynamic tag.
let sr = stringify::stringify_at_runtime(quote! { __yew_v });
@ -361,10 +362,10 @@ impl ToTokens for HtmlTag {
None
};
tokens.extend(quote_spanned! {tag_name.span()=>
tokens.extend(quote_spanned! {name.span()=>
{
#[allow(unused_braces)]
let mut #vtag = ::yew::virtual_dom::VTag::new(#name);
let mut #vtag = ::yew::virtual_dom::VTag::new(#name_sr);
#set_node_ref
#set_key
@ -416,7 +417,7 @@ impl Peek<'_, ()> for DynamicName {
}
impl Parse for DynamicName {
fn parse(input: ParseStream) -> ParseResult<Self> {
fn parse(input: ParseStream) -> syn::Result<Self> {
let at = input.parse()?;
// the expression block is optional, closing tags don't have it.
let expr = if input.cursor().group(Delimiter::Brace).is_some() {
@ -467,7 +468,7 @@ impl Peek<'_, TagKey> for TagName {
}
impl Parse for TagName {
fn parse(input: ParseStream) -> ParseResult<Self> {
fn parse(input: ParseStream) -> syn::Result<Self> {
if DynamicName::peek(input.cursor()).is_some() {
DynamicName::parse(input).map(Self::Expr)
} else {
@ -485,25 +486,32 @@ impl ToTokens for TagName {
}
}
struct HtmlTagOpen {
lt: Token![<],
tag_name: TagName,
attributes: TagAttributes,
div: Option<Token![/]>,
gt: Token![>],
struct HtmlElementOpen {
tag: TagTokens,
name: TagName,
props: ElementProps,
}
impl HtmlElementOpen {
fn is_self_closing(&self) -> bool {
self.tag.div.is_some()
}
fn to_spanned(&self) -> impl ToTokens {
self.tag.to_spanned()
}
}
impl PeekValue<TagKey> for HtmlTagOpen {
impl PeekValue<TagKey> for HtmlElementOpen {
fn peek(cursor: Cursor) -> Option<TagKey> {
let (punct, cursor) = cursor.punct()?;
(punct.as_char() == '<').as_option()?;
let (tag_key, cursor) = TagName::peek(cursor)?;
if let TagKey::Lit(name) = &tag_key {
// Avoid parsing `<key=[...]>` as an HtmlTag. It needs to be parsed as an HtmlList.
// Avoid parsing `<key=[...]>` as an element. It needs to be parsed as an `HtmlList`.
if name.to_string() == "key" {
let (punct, _) = cursor.punct()?;
// ... unless it isn't followed by a '='. `<key></key>` is a valid HtmlTag!
// ... unless it isn't followed by a '='. `<key></key>` is a valid element!
(punct.as_char() != '=').as_option()?;
} else {
non_capitalized_ascii(&name.to_string()).as_option()?;
@ -514,65 +522,51 @@ impl PeekValue<TagKey> for HtmlTagOpen {
}
}
impl Parse for HtmlTagOpen {
fn parse(input: ParseStream) -> ParseResult<Self> {
let lt = input.parse::<Token![<]>()?;
let tag_name = input.parse::<TagName>()?;
let TagSuffix { stream, div, gt } = input.parse()?;
let mut attributes: TagAttributes = syn::parse2(stream)?;
impl Parse for HtmlElementOpen {
fn parse(input: ParseStream) -> syn::Result<Self> {
TagTokens::parse_start_content(input, |input, tag| {
let name = input.parse::<TagName>()?;
let mut props = input.parse::<ElementProps>()?;
match &tag_name {
TagName::Lit(name) => {
// Don't treat value as special for non input / textarea fields
// For dynamic tags this is done at runtime!
match name.to_ascii_lowercase_string().as_str() {
"input" | "textarea" => {}
_ => {
if let Some(attr) = attributes.value.take() {
attributes.attributes.push(TagAttribute {
label: HtmlDashedName::new(Ident::new("value", Span::call_site())),
question_mark: attr.question_mark,
value: attr.value,
});
match &name {
TagName::Lit(name) => {
// Don't treat value as special for non input / textarea fields
// For dynamic tags this is done at runtime!
match name.to_ascii_lowercase_string().as_str() {
"input" | "textarea" => {}
_ => {
if let Some(attr) = props.value.take() {
props.attributes.push(attr);
}
}
}
}
}
TagName::Expr(name) => {
if name.expr.is_none() {
return Err(syn::Error::new_spanned(
tag_name,
"this dynamic tag is missing an expression block defining its value",
));
TagName::Expr(name) => {
if name.expr.is_none() {
return Err(syn::Error::new_spanned(
name,
"this dynamic tag is missing an expression block defining its value",
));
}
}
}
}
Ok(HtmlTagOpen {
lt,
tag_name,
attributes,
div,
gt,
Ok(Self { tag, name, props })
})
}
}
impl ToTokens for HtmlTagOpen {
fn to_tokens(&self, tokens: &mut TokenStream) {
let HtmlTagOpen { lt, gt, .. } = self;
tokens.extend(quote! {#lt#gt});
struct HtmlElementClose {
tag: TagTokens,
_name: TagName,
}
impl HtmlElementClose {
fn to_spanned(&self) -> impl ToTokens {
self.tag.to_spanned()
}
}
struct HtmlTagClose {
lt: Token![<],
div: Option<Token![/]>,
tag_name: TagName,
gt: Token![>],
}
impl PeekValue<TagKey> for HtmlTagClose {
impl PeekValue<TagKey> for HtmlElementClose {
fn peek(cursor: Cursor) -> Option<TagKey> {
let (punct, cursor) = cursor.punct()?;
(punct.as_char() == '<').as_option()?;
@ -592,39 +586,21 @@ impl PeekValue<TagKey> for HtmlTagClose {
}
}
impl Parse for HtmlTagClose {
fn parse(input: ParseStream) -> ParseResult<Self> {
let lt = input.parse()?;
let div = input.parse()?;
let tag_name = input.parse()?;
let gt = input.parse()?;
impl Parse for HtmlElementClose {
fn parse(input: ParseStream) -> syn::Result<Self> {
TagTokens::parse_end_content(input, |input, tag| {
let name = input.parse()?;
if let TagName::Expr(name) = &tag_name {
if let Some(expr) = &name.expr {
return Err(syn::Error::new_spanned(
if let TagName::Expr(name) = &name {
if let Some(expr) = &name.expr {
return Err(syn::Error::new_spanned(
expr,
"dynamic closing tags must not have a body (hint: replace it with just `</@>`)",
));
}
}
}
Ok(HtmlTagClose {
lt,
div,
tag_name,
gt,
Ok(Self { tag, _name: name })
})
}
}
impl ToTokens for HtmlTagClose {
fn to_tokens(&self, tokens: &mut TokenStream) {
let HtmlTagClose {
lt,
div,
tag_name,
gt,
} = self;
tokens.extend(quote! {#lt#div#tag_name#gt});
}
}

View File

@ -4,7 +4,7 @@ use boolinator::Boolinator;
use proc_macro2::TokenStream;
use quote::{quote_spanned, ToTokens};
use syn::buffer::Cursor;
use syn::parse::{Parse, ParseStream, Result as ParseResult};
use syn::parse::{Parse, ParseStream};
use syn::spanned::Spanned;
use syn::{Expr, Token};
@ -18,7 +18,7 @@ impl PeekValue<()> for HtmlIterable {
}
impl Parse for HtmlIterable {
fn parse(input: ParseStream) -> ParseResult<Self> {
fn parse(input: ParseStream) -> syn::Result<Self> {
let for_token = input.parse::<Token![for]>()?;
match input.parse() {

View File

@ -1,12 +1,11 @@
use super::{html_dashed_name::HtmlDashedName, HtmlChildrenTree};
use crate::html_tree::{HtmlProp, HtmlPropSuffix};
use crate::{Peek, PeekValue};
use super::{html_dashed_name::HtmlDashedName, HtmlChildrenTree, TagTokens};
use crate::{props::Prop, Peek, PeekValue};
use boolinator::Boolinator;
use quote::{quote, quote_spanned, ToTokens};
use syn::buffer::Cursor;
use syn::parse::{Parse, ParseStream, Result as ParseResult};
use syn::parse::{Parse, ParseStream};
use syn::spanned::Spanned;
use syn::{Expr, Token};
use syn::Expr;
pub struct HtmlList {
open: HtmlListOpen,
@ -23,11 +22,11 @@ impl PeekValue<()> for HtmlList {
}
impl Parse for HtmlList {
fn parse(input: ParseStream) -> ParseResult<Self> {
fn parse(input: ParseStream) -> syn::Result<Self> {
if HtmlListClose::peek(input.cursor()).is_some() {
return match input.parse::<HtmlListClose>() {
Ok(close) => Err(syn::Error::new_spanned(
close,
close.to_spanned(),
"this closing fragment has no corresponding opening fragment",
)),
Err(err) => Err(err),
@ -40,7 +39,7 @@ impl Parse for HtmlList {
children.parse_child(input)?;
if input.is_empty() {
return Err(syn::Error::new_spanned(
open,
open.to_spanned(),
"this opening fragment has no corresponding closing fragment",
));
}
@ -48,7 +47,7 @@ impl Parse for HtmlList {
let close = input.parse::<HtmlListClose>()?;
Ok(HtmlList {
Ok(Self {
open,
children,
close,
@ -70,8 +69,13 @@ impl ToTokens for HtmlList {
quote! {None}
};
let open_close_tokens = quote! {#open#close};
tokens.extend(quote_spanned! {open_close_tokens.span()=>
let spanned = {
let open = open.to_spanned();
let close = close.to_spanned();
quote! { #open#close }
};
tokens.extend(quote_spanned! {spanned.span()=>
::yew::virtual_dom::VNode::VList(
::yew::virtual_dom::VList::new_with_children(#children, #key)
)
@ -80,9 +84,13 @@ impl ToTokens for HtmlList {
}
struct HtmlListOpen {
lt: Token![<],
tag: TagTokens,
props: HtmlListProps,
gt: Token![>],
}
impl HtmlListOpen {
fn to_spanned(&self) -> impl ToTokens {
self.tag.to_spanned()
}
}
impl PeekValue<()> for HtmlListOpen {
@ -101,18 +109,11 @@ impl PeekValue<()> for HtmlListOpen {
}
impl Parse for HtmlListOpen {
fn parse(input: ParseStream) -> ParseResult<Self> {
let lt = input.parse()?;
let HtmlPropSuffix { stream, gt, .. } = input.parse()?;
let props = syn::parse2(stream)?;
Ok(Self { lt, props, gt })
}
}
impl ToTokens for HtmlListOpen {
fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) {
let HtmlListOpen { lt, gt, .. } = self;
tokens.extend(quote! {#lt#gt});
fn parse(input: ParseStream) -> syn::Result<Self> {
TagTokens::parse_start_content(input, |input, tag| {
let props = input.parse()?;
Ok(Self { tag, props })
})
}
}
@ -120,11 +121,11 @@ struct HtmlListProps {
key: Option<Expr>,
}
impl Parse for HtmlListProps {
fn parse(input: ParseStream) -> ParseResult<Self> {
fn parse(input: ParseStream) -> syn::Result<Self> {
let key = if input.is_empty() {
None
} else {
let prop: HtmlProp = input.parse()?;
let prop: Prop = input.parse()?;
if !input.is_empty() {
return Err(input.error("only a single `key` prop is allowed on a fragment"));
}
@ -145,12 +146,12 @@ impl Parse for HtmlListProps {
}
}
struct HtmlListClose {
lt: Token![<],
div: Token![/],
gt: Token![>],
struct HtmlListClose(TagTokens);
impl HtmlListClose {
fn to_spanned(&self) -> impl ToTokens {
self.0.to_spanned()
}
}
impl PeekValue<()> for HtmlListClose {
fn peek(cursor: Cursor) -> Option<()> {
let (punct, cursor) = cursor.punct()?;
@ -162,20 +163,14 @@ impl PeekValue<()> for HtmlListClose {
(punct.as_char() == '>').as_option()
}
}
impl Parse for HtmlListClose {
fn parse(input: ParseStream) -> ParseResult<Self> {
Ok(HtmlListClose {
lt: input.parse()?,
div: input.parse()?,
gt: input.parse()?,
fn parse(input: ParseStream) -> syn::Result<Self> {
TagTokens::parse_end_content(input, |input, tag| {
if !input.is_empty() {
Err(input.error("unexpected content in list close"))
} else {
Ok(Self(tag))
}
})
}
}
impl ToTokens for HtmlListClose {
fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) {
let HtmlListClose { lt, div, gt } = self;
tokens.extend(quote! {#lt#div#gt});
}
}

View File

@ -1,106 +0,0 @@
use crate::html_tree::HtmlDashedName as HtmlPropLabel;
use crate::{Peek, PeekValue};
use proc_macro2::{TokenStream, TokenTree};
use syn::buffer::Cursor;
use syn::parse::{Parse, ParseStream, Result as ParseResult};
use syn::{Expr, Token};
pub struct HtmlProp {
pub label: HtmlPropLabel,
pub question_mark: Option<Token![?]>,
pub value: Expr,
}
impl HtmlProp {
/// Checks if the prop uses the optional attribute syntax.
/// If it does, an error is returned.
pub fn ensure_not_optional(&self) -> syn::Result<()> {
if self.question_mark.is_some() {
let msg = format!(
"the `{}` attribute does not support being used as an optional attribute",
self.label
);
Err(syn::Error::new_spanned(&self.label, msg))
} else {
Ok(())
}
}
}
impl PeekValue<()> for HtmlProp {
fn peek(cursor: Cursor) -> Option<()> {
HtmlPropLabel::peek(cursor).map(|_| ())
}
}
impl Parse for HtmlProp {
fn parse(input: ParseStream) -> ParseResult<Self> {
let label = input.parse::<HtmlPropLabel>()?;
let question_mark = input.parse::<Token![?]>().ok();
let equals = input
.parse::<Token![=]>()
.map_err(|_| syn::Error::new_spanned(&label, "this prop doesn't have a value"))?;
if input.is_empty() {
return Err(syn::Error::new_spanned(
equals,
"expected an expression following this equals sign",
));
}
let value = input.parse::<Expr>()?;
// backwards compat
let _ = input.parse::<Token![,]>();
Ok(Self {
label,
question_mark,
value,
})
}
}
pub struct HtmlPropSuffix {
pub stream: TokenStream,
pub div: Option<Token![/]>,
pub gt: Token![>],
}
impl Parse for HtmlPropSuffix {
fn parse(input: ParseStream) -> ParseResult<Self> {
let mut trees: Vec<TokenTree> = vec![];
let mut div: Option<Token![/]> = None;
let mut angle_count = 1;
let gt: Option<Token![>]>;
loop {
let next = input.parse()?;
if let TokenTree::Punct(punct) = &next {
match punct.as_char() {
'>' => {
angle_count -= 1;
if angle_count == 0 {
gt = Some(syn::token::Gt {
spans: [punct.span()],
});
break;
}
}
'<' => angle_count += 1,
'/' => {
if angle_count == 1 && input.peek(Token![>]) {
div = Some(syn::token::Div {
spans: [punct.span()],
});
gt = Some(input.parse()?);
break;
}
}
_ => {}
};
}
trees.push(next);
}
let gt: Token![>] = gt.ok_or_else(|| input.error("missing tag close"))?;
let stream: TokenStream = trees.into_iter().collect();
Ok(HtmlPropSuffix { stream, div, gt })
}
}

View File

@ -1,33 +1,33 @@
mod html_block;
mod html_component;
mod html_dashed_name;
mod html_iterable;
mod html_list;
mod html_node;
mod html_prop;
mod html_tag;
use crate::PeekValue;
use html_block::HtmlBlock;
use html_component::HtmlComponent;
use html_dashed_name::HtmlDashedName;
use html_iterable::HtmlIterable;
use html_list::HtmlList;
use html_node::HtmlNode;
use html_prop::HtmlProp;
use html_prop::HtmlPropSuffix;
use html_tag::HtmlTag;
use proc_macro2::{Ident, Span, TokenStream};
use quote::{quote, quote_spanned, ToTokens};
use syn::buffer::Cursor;
use syn::parse::{Parse, ParseStream, Result};
use syn::spanned::Spanned;
mod html_block;
mod html_component;
mod html_dashed_name;
mod html_element;
mod html_iterable;
mod html_list;
mod html_node;
mod tag;
use html_block::HtmlBlock;
use html_component::HtmlComponent;
pub use html_dashed_name::HtmlDashedName;
use html_element::HtmlElement;
use html_iterable::HtmlIterable;
use html_list::HtmlList;
use html_node::HtmlNode;
use tag::TagTokens;
pub enum HtmlType {
Block,
Component,
List,
Tag,
Element,
Empty,
}
@ -35,7 +35,7 @@ pub enum HtmlTree {
Block(Box<HtmlBlock>),
Component(Box<HtmlComponent>),
List(Box<HtmlList>),
Tag(Box<HtmlTag>),
Element(Box<HtmlElement>),
Empty,
}
@ -46,7 +46,7 @@ impl Parse for HtmlTree {
let html_tree = match html_type {
HtmlType::Empty => HtmlTree::Empty,
HtmlType::Component => HtmlTree::Component(Box::new(input.parse()?)),
HtmlType::Tag => HtmlTree::Tag(Box::new(input.parse()?)),
HtmlType::Element => HtmlTree::Element(Box::new(input.parse()?)),
HtmlType::Block => HtmlTree::Block(Box::new(input.parse()?)),
HtmlType::List => HtmlTree::List(Box::new(input.parse()?)),
};
@ -62,8 +62,8 @@ impl PeekValue<HtmlType> for HtmlTree {
Some(HtmlType::List)
} else if HtmlComponent::peek(cursor).is_some() {
Some(HtmlType::Component)
} else if HtmlTag::peek(cursor).is_some() {
Some(HtmlType::Tag)
} else if HtmlElement::peek(cursor).is_some() {
Some(HtmlType::Element)
} else if HtmlBlock::peek(cursor).is_some() {
Some(HtmlType::Block)
} else {
@ -79,7 +79,7 @@ impl ToTokens for HtmlTree {
::yew::virtual_dom::VNode::VList(::yew::virtual_dom::VList::new())
}),
HtmlTree::Component(comp) => comp.to_tokens(tokens),
HtmlTree::Tag(tag) => tag.to_tokens(tokens),
HtmlTree::Element(tag) => tag.to_tokens(tokens),
HtmlTree::List(list) => list.to_tokens(tokens),
HtmlTree::Block(block) => block.to_tokens(tokens),
}

View File

@ -0,0 +1,150 @@
use proc_macro2::{Span, TokenStream, TokenTree};
use quote::{quote, ToTokens};
use syn::{
parse::{ParseStream, Parser},
Token,
};
/// Check whether two spans are equal.
/// The implementation is really silly but I couldn't find another way to do it on stable.
/// This check isn't required to be fully accurate so it's not the end of the world if it breaks.
fn span_eq_hack(a: &Span, b: &Span) -> bool {
format!("{:?}", a) == format!("{:?}", b)
}
/// Change all occurrences of span `from` to `to` in the given error.
fn error_replace_span(err: syn::Error, from: Span, to: impl ToTokens) -> syn::Error {
let err_it = err.into_iter().map(|err| {
if span_eq_hack(&err.span(), &from) {
syn::Error::new_spanned(&to, err.to_string())
} else {
err
}
});
// SAFETY: all errors have at least one message
crate::join_errors(err_it).unwrap_err()
}
/// Helper type for parsing HTML tags.
/// The struct only stores the associated tokens, not the content of the tag.
/// This is meant to mirror the design of delimiters in `syn`.
pub struct TagTokens {
pub lt: Token![<],
pub div: Option<Token![/]>,
pub gt: Token![>],
}
impl TagTokens {
/// Parse the content of a start tag.
/// The given parse function is called with a `ParseStream`
/// containing only the contents of the tag and surrounding `TagTokens`.
pub fn parse_start_content<T>(
input: ParseStream,
parse: impl FnOnce(ParseStream, Self) -> syn::Result<T>,
) -> syn::Result<T> {
Self::parse_content(Self::parse_start(input)?, parse)
}
/// Same as `parse_start_content` but for end tags.
pub fn parse_end_content<T>(
input: ParseStream,
parse: impl FnOnce(ParseStream, Self) -> syn::Result<T>,
) -> syn::Result<T> {
Self::parse_content(Self::parse_end(input)?, parse)
}
fn parse_content<T>(
(tag, content): (Self, TokenStream),
parse: impl FnOnce(ParseStream, Self) -> syn::Result<T>,
) -> syn::Result<T> {
let scope_spanned = tag.to_spanned();
let content_parser = |input: ParseStream| {
parse(input, tag).map_err(|err| {
// we can't modify the scope span used by `ParseStream`. It just uses the call site by default.
// The scope span is used when an error can't be attributed to a token tree (ex. when the input is empty).
// We rewrite all spans to point at the tag which at least narrows down the correct location.
// It's not ideal, but it'll have to do until `syn` gives us more access.
error_replace_span(err, Span::call_site(), &scope_spanned)
})
};
content_parser.parse2(content)
}
/// Parse a start tag
fn parse_start(input: ParseStream) -> syn::Result<(Self, TokenStream)> {
let lt = input.parse()?;
let (content, div, gt) = Self::parse_until_end(input)?;
Ok((Self { lt, div, gt }, content))
}
/// Parse an end tag.
/// `div` will always be `Some` for end tags.
fn parse_end(input: ParseStream) -> syn::Result<(Self, TokenStream)> {
let lt = input.parse()?;
let div = Some(input.parse()?);
let (content, end_div, gt) = Self::parse_until_end(input)?;
if end_div.is_some() {
return Err(syn::Error::new_spanned(
end_div,
"unexpected `/` in this end tag",
));
}
Ok((Self { lt, div, gt }, content))
}
fn parse_until_end(
input: ParseStream,
) -> syn::Result<(TokenStream, Option<Token![/]>, Token![>])> {
let mut inner_trees = Vec::new();
let mut angle_count: usize = 1;
let mut div: Option<Token![/]> = None;
let gt: Token![>];
loop {
let next = input.parse()?;
if let TokenTree::Punct(punct) = &next {
match punct.as_char() {
'/' => {
if angle_count == 1 && input.peek(Token![>]) {
div = Some(syn::token::Div {
spans: [punct.span()],
});
gt = input.parse()?;
break;
}
}
'>' => {
angle_count = angle_count.checked_sub(1).ok_or_else(|| {
syn::Error::new_spanned(
punct,
"this tag close has no corresponding tag open",
)
})?;
if angle_count == 0 {
gt = syn::token::Gt {
spans: [punct.span()],
};
break;
}
}
'<' => angle_count += 1,
_ => {}
};
}
inner_trees.push(next);
}
Ok((inner_trees.into_iter().collect(), div, gt))
}
/// Generate tokens which can be used in `syn::Error::new_spanned` to span the entire tag.
/// This is to work around the limitation of being unable to manually join spans on stable.
pub fn to_spanned(&self) -> impl ToTokens {
let Self { lt, gt, .. } = self;
quote! {#lt#gt}
}
}

View File

@ -55,16 +55,15 @@
//!
//! Please refer to [https://github.com/yewstack/yew](https://github.com/yewstack/yew) for how to set this up.
#![recursion_limit = "128"]
mod derive_props;
mod html_tree;
mod props;
mod stringify;
use derive_props::DerivePropsInput;
use html_tree::{HtmlRoot, HtmlRootVNode};
use proc_macro::TokenStream;
use quote::{quote, ToTokens};
use quote::ToTokens;
use syn::buffer::Cursor;
use syn::parse_macro_input;
@ -86,6 +85,17 @@ fn non_capitalized_ascii(string: &str) -> bool {
}
}
/// Combine multiple `syn` errors into a single one.
/// Returns `Result::Ok` if the given iterator is empty
fn join_errors(mut it: impl Iterator<Item = syn::Error>) -> syn::Result<()> {
it.next().map_or(Ok(()), |mut err| {
for other in it {
err.combine(other);
}
Err(err)
})
}
#[proc_macro_derive(Properties, attributes(prop_or, prop_or_else, prop_or_default))]
pub fn derive_props(input: TokenStream) -> TokenStream {
let input = parse_macro_input!(input as DerivePropsInput);
@ -95,11 +105,17 @@ pub fn derive_props(input: TokenStream) -> TokenStream {
#[proc_macro]
pub fn html_nested(input: TokenStream) -> TokenStream {
let root = parse_macro_input!(input as HtmlRoot);
TokenStream::from(quote! {#root})
TokenStream::from(root.into_token_stream())
}
#[proc_macro]
pub fn html(input: TokenStream) -> TokenStream {
let root = parse_macro_input!(input as HtmlRootVNode);
TokenStream::from(quote! {#root})
TokenStream::from(root.into_token_stream())
}
#[proc_macro]
pub fn props(input: TokenStream) -> TokenStream {
let props = parse_macro_input!(input as props::PropsMacroInput);
TokenStream::from(props.into_token_stream())
}

View File

@ -0,0 +1,218 @@
use super::{Prop, Props, SpecialProps};
use proc_macro2::{Ident, TokenStream, TokenTree};
use quote::{quote, quote_spanned, ToTokens};
use std::convert::TryFrom;
use syn::{
parse::{Parse, ParseStream},
spanned::Spanned,
Expr, Token,
};
mod kw {
syn::custom_keyword!(with);
}
pub struct WithProps {
pub special: SpecialProps,
pub with: kw::with,
pub expr: Expr,
}
impl WithProps {
/// Check if the `ParseStream` contains a `with expr` expression.
/// This function advances the given `ParseStream`!
fn contains_with_expr(input: ParseStream) -> bool {
while !input.is_empty() {
if input.peek(kw::with) && !input.peek2(Token![=]) {
return true;
}
input.parse::<TokenTree>().ok();
}
false
}
}
impl Parse for WithProps {
fn parse(input: ParseStream) -> syn::Result<Self> {
let mut special = SpecialProps::default();
let mut with_expr: Option<(kw::with, Expr)> = None;
while !input.is_empty() {
// no need to check if it's followed by `=` because `with` isn't a special prop
if input.peek(kw::with) {
let with = input.parse::<kw::with>()?;
if input.is_empty() {
return Err(syn::Error::new_spanned(
with,
"expected expression following this `with`",
));
}
with_expr = Some((with, input.parse()?));
} else {
let prop = input.parse::<Prop>()?;
if let Some(slot) = special.get_slot_mut(&prop.label.to_string()) {
if slot.is_some() {
return Err(syn::Error::new_spanned(
&prop.label,
&format!("`{}` can only be set once", prop.label),
));
}
slot.replace(prop);
} else {
return Err(syn::Error::new_spanned(
prop.label,
"Using the `with props` syntax in combination with named props is not allowed (note: this does not apply to special props like `ref` and `key`)",
));
}
}
}
let (with, expr) =
with_expr.ok_or_else(|| input.error("missing `with props` expression"))?;
Ok(Self {
special,
with,
expr,
})
}
}
pub enum ComponentProps {
List(Props),
With(Box<WithProps>),
}
impl ComponentProps {
/// Get the special props supported by both variants
pub fn special(&self) -> &SpecialProps {
match self {
Self::List(props) => &props.special,
Self::With(props) => &props.special,
}
}
fn prop_validation_tokens(&self, props_ty: impl ToTokens, has_children: bool) -> TokenStream {
let check_children = if has_children {
Some(quote_spanned! {props_ty.span()=> __yew_props.children; })
} else {
None
};
let check_props = match self {
Self::List(props) => props
.iter()
.map(|Prop { label, .. }| {
quote_spanned! {label.span()=> __yew_props.#label; }
})
.collect(),
Self::With(with_props) => {
let expr = &with_props.expr;
quote_spanned! {props_ty.span()=>
let _: #props_ty = #expr;
}
}
};
quote_spanned! {props_ty.span()=>
#[allow(clippy::no_effect)]
if false {
let _ = |__yew_props: #props_ty| {
#check_children
#check_props
};
}
}
}
pub fn build_properties_tokens<CR: ToTokens>(
&self,
props_ty: impl ToTokens,
children_renderer: Option<CR>,
) -> TokenStream {
let validate_props = self.prop_validation_tokens(&props_ty, children_renderer.is_some());
let build_props = match self {
Self::List(props) => {
let set_props = props.iter().map(|Prop { label, value, .. }| {
quote_spanned! {value.span()=> .#label(
#[allow(unused_braces)]
<::yew::virtual_dom::VComp as ::yew::virtual_dom::Transformer<_, _>>::transform(
#value
)
)}
});
let set_children = if let Some(children) = children_renderer {
Some(quote_spanned! {props_ty.span()=>
.children(#children)
})
} else {
None
};
quote_spanned! {props_ty.span()=>
<#props_ty as ::yew::html::Properties>::builder()
#(#set_props)*
#set_children
.build()
}
}
Self::With(with_props) => {
let ident = Ident::new("__yew_props", props_ty.span());
let set_children = if let Some(children) = children_renderer {
Some(quote_spanned! {props_ty.span()=>
#ident.children = #children;
})
} else {
None
};
let expr = &with_props.expr;
quote! {
let mut #ident = #expr;
#set_children
#ident
}
}
};
quote! {
{
#validate_props
#build_props
}
}
}
}
impl Parse for ComponentProps {
fn parse(input: ParseStream) -> syn::Result<Self> {
if WithProps::contains_with_expr(&input.fork()) {
input.parse().map(Self::With)
} else {
input.parse::<Props>().and_then(Self::try_from)
}
}
}
impl TryFrom<Props> for ComponentProps {
type Error = syn::Error;
fn try_from(props: Props) -> Result<Self, Self::Error> {
props.check_no_duplicates()?;
props.check_all(|prop| {
if prop.question_mark.is_some() {
Err(syn::Error::new_spanned(
&prop.label,
"optional attributes are only supported on elements. Components can use `Option<T>` properties to accomplish the same thing.",
))
} else if !prop.label.extended.is_empty() {
Err(syn::Error::new_spanned(
&prop.label,
"expected a valid Rust identifier",
))
} else {
Ok(())
}
})?;
Ok(Self::List(props))
}
}

View File

@ -1,27 +1,95 @@
use crate::html_tree::HtmlProp as TagAttribute;
use crate::PeekValue;
use super::{Prop, Props, SpecialProps};
use lazy_static::lazy_static;
use std::collections::HashSet;
use std::iter::FromIterator;
use syn::parse::{Parse, ParseStream, Result as ParseResult};
use syn::parse::{Parse, ParseStream};
use syn::{Expr, ExprTuple};
pub struct TagAttributes {
pub attributes: Vec<TagAttribute>,
pub listeners: Vec<TagAttribute>,
pub classes: Option<ClassesForm>,
pub booleans: Vec<TagAttribute>,
pub value: Option<TagAttribute>,
pub kind: Option<TagAttribute>,
pub checked: Option<Expr>,
pub node_ref: Option<Expr>,
pub key: Option<Expr>,
}
pub enum ClassesForm {
Tuple(Vec<Expr>),
Single(Box<Expr>),
}
impl ClassesForm {
fn from_expr(expr: Expr) -> Self {
match expr {
Expr::Tuple(ExprTuple { elems, .. }) => ClassesForm::Tuple(elems.into_iter().collect()),
expr => ClassesForm::Single(Box::new(expr)),
}
}
}
pub struct ElementProps {
pub attributes: Vec<Prop>,
pub listeners: Vec<Prop>,
pub classes: Option<ClassesForm>,
pub booleans: Vec<Prop>,
pub value: Option<Prop>,
pub kind: Option<Prop>,
pub checked: Option<Prop>,
pub node_ref: Option<Prop>,
pub key: Option<Prop>,
}
impl Parse for ElementProps {
fn parse(input: ParseStream) -> syn::Result<Self> {
let mut props = input.parse::<Props>()?;
let listeners =
props.drain_filter(|prop| LISTENER_SET.contains(prop.label.to_string().as_str()));
#[cfg(feature = "std_web")]
listeners.check_all(|prop| {
let label = &prop.label;
if UNSUPPORTED_LISTENER_SET.contains(label.to_string().as_str()) {
Err(syn::Error::new_spanned(
&label,
format!(
"the listener `{}` is only available when using web-sys",
&label
),
))
} else {
Ok(())
}
})?;
// Multiple listener attributes are allowed, but no others
props.check_no_duplicates()?;
let booleans =
props.drain_filter(|prop| BOOLEAN_SET.contains(prop.label.to_string().as_str()));
booleans.check_all(|prop| {
if prop.question_mark.is_some() {
Err(syn::Error::new_spanned(
&prop.label,
"boolean attributes don't support being used as an optional attribute (hint: a value of false results in the attribute not being set)"
))
} else {
Ok(())
}
})?;
let classes = props
.pop_nonoptional("class")?
.map(|prop| ClassesForm::from_expr(prop.value));
let value = props.pop("value");
let kind = props.pop("type");
let checked = props.pop_nonoptional("checked")?;
let SpecialProps { node_ref, key } = props.special;
Ok(Self {
attributes: props.prop_list.into_vec(),
classes,
listeners: listeners.into_vec(),
checked,
booleans: booleans.into_vec(),
value,
kind,
node_ref,
key,
})
}
}
lazy_static! {
static ref BOOLEAN_SET: HashSet<&'static str> = {
@ -211,138 +279,3 @@ lazy_static! {
)
};
}
impl TagAttributes {
fn drain_listeners(attrs: &mut Vec<TagAttribute>) -> Vec<TagAttribute> {
let mut i = 0;
let mut drained = Vec::new();
while i < attrs.len() {
let name_str = attrs[i].label.to_string();
if LISTENER_SET.contains(&name_str.as_str()) {
drained.push(attrs.remove(i));
} else {
i += 1;
}
}
drained
}
fn drain_boolean(attrs: &mut Vec<TagAttribute>) -> Vec<TagAttribute> {
let mut i = 0;
let mut drained = Vec::new();
while i < attrs.len() {
let name_str = attrs[i].label.to_string();
if BOOLEAN_SET.contains(&name_str.as_str()) {
drained.push(attrs.remove(i));
} else {
i += 1;
}
}
drained
}
fn remove_attr(attrs: &mut Vec<TagAttribute>, name: &str) -> Option<TagAttribute> {
let mut i = 0;
while i < attrs.len() {
if attrs[i].label.to_string() == name {
return Some(attrs.remove(i));
} else {
i += 1;
}
}
None
}
fn remove_attr_nonoptional(
attrs: &mut Vec<TagAttribute>,
name: &str,
) -> syn::Result<Option<TagAttribute>> {
match Self::remove_attr(attrs, name) {
Some(attr) => attr.ensure_not_optional().map(|_| Some(attr)),
None => Ok(None),
}
}
fn map_classes(class_expr: Expr) -> ClassesForm {
match class_expr {
Expr::Tuple(ExprTuple { elems, .. }) => ClassesForm::Tuple(elems.into_iter().collect()),
expr => ClassesForm::Single(Box::new(expr)),
}
}
}
impl Parse for TagAttributes {
fn parse(input: ParseStream) -> ParseResult<Self> {
let mut attributes: Vec<TagAttribute> = Vec::new();
while TagAttribute::peek(input.cursor()).is_some() {
attributes.push(input.parse::<TagAttribute>()?);
}
let mut listeners = Vec::new();
for listener in Self::drain_listeners(&mut attributes) {
#[cfg(feature = "std_web")]
{
let label = &listener.label;
if UNSUPPORTED_LISTENER_SET.contains(&label.to_string().as_str()) {
return Err(syn::Error::new_spanned(
&label,
format!(
"the listener `{}` is only available when using web-sys",
&label
),
));
}
}
listeners.push(listener);
}
// Multiple listener attributes are allowed, but no others
attributes.sort_by(|a, b| {
a.label
.to_string()
.partial_cmp(&b.label.to_string())
.unwrap()
});
let mut i = 0;
while i + 1 < attributes.len() {
if attributes[i].label.to_string() == attributes[i + 1].label.to_string() {
let label = &attributes[i + 1].label;
return Err(syn::Error::new_spanned(
label,
format!("the attribute `{}` can only be specified once", label),
));
}
i += 1;
}
let booleans = Self::drain_boolean(&mut attributes);
for attr in &booleans {
if attr.question_mark.is_some() {
return Err(syn::Error::new_spanned(
&attr.label,
"boolean attributes don't support being used as an optional attribute (hint: a value of false results in the attribute not being set)"
));
}
}
let classes = Self::remove_attr_nonoptional(&mut attributes, "class")?
.map(|a| Self::map_classes(a.value));
let value = Self::remove_attr(&mut attributes, "value");
let kind = Self::remove_attr(&mut attributes, "type");
let checked = Self::remove_attr_nonoptional(&mut attributes, "checked")?.map(|v| v.value);
let node_ref = Self::remove_attr_nonoptional(&mut attributes, "ref")?.map(|v| v.value);
let key = Self::remove_attr_nonoptional(&mut attributes, "key")?.map(|v| v.value);
Ok(Self {
attributes,
classes,
listeners,
checked,
booleans,
value,
kind,
node_ref,
key,
})
}
}

View File

@ -0,0 +1,9 @@
mod component;
mod element;
mod prop;
mod prop_macro;
pub use component::*;
pub use element::*;
pub use prop::*;
pub use prop_macro::PropsMacroInput;

280
yew-macro/src/props/prop.rs Normal file
View File

@ -0,0 +1,280 @@
use crate::html_tree::HtmlDashedName;
use proc_macro2::TokenStream;
use quote::{quote, ToTokens};
use std::{
cmp::Ordering,
convert::TryFrom,
ops::{Deref, DerefMut},
};
use syn::{
parse::{Parse, ParseStream},
Expr, Token,
};
pub enum PropPunct {
Eq(Token![=]),
Colon(Token![:]),
}
impl ToTokens for PropPunct {
fn to_tokens(&self, tokens: &mut TokenStream) {
match self {
Self::Eq(p) => p.to_tokens(tokens),
Self::Colon(p) => p.to_tokens(tokens),
}
}
}
pub struct Prop {
pub label: HtmlDashedName,
pub question_mark: Option<Token![?]>,
/// Punctuation between `label` and `value`.
pub punct: Option<PropPunct>,
pub value: Expr,
}
impl Prop {
/// Checks if the prop uses the optional attribute syntax.
/// If it does, an error is returned.
pub fn ensure_not_optional(&self) -> syn::Result<()> {
let Self {
label,
question_mark,
punct,
..
} = self;
if question_mark.is_some() {
let msg = format!(
"`{}` does not support being used as an optional attribute",
label
);
// include `?=` in the span
Err(syn::Error::new_spanned(
quote! { #label#question_mark#punct },
msg,
))
} else {
Ok(())
}
}
}
impl Parse for Prop {
fn parse(input: ParseStream) -> syn::Result<Self> {
let label = input.parse::<HtmlDashedName>()?;
let question_mark = input.parse::<Token![?]>().ok();
let equals = input.parse::<Token![=]>().map_err(|_| {
syn::Error::new_spanned(
&label,
format!("`{}` doesn't have a value. (hint: set the value to `true` or `false` for boolean attributes)", label),
)
})?;
if input.is_empty() {
return Err(syn::Error::new_spanned(
equals,
"expected an expression following this equals sign",
));
}
let value = input.parse::<Expr>()?;
Ok(Self {
label,
question_mark,
punct: Some(PropPunct::Eq(equals)),
value,
})
}
}
/// List of props sorted in alphabetical order*.
///
/// \*The "children" prop always comes last to match the behaviour of the `Properties` derive macro.
///
/// The list may contain multiple props with the same label.
/// Use `check_no_duplicates` to ensure that there are no duplicates.
pub struct SortedPropList(Vec<Prop>);
impl SortedPropList {
const CHILDREN_LABEL: &'static str = "children";
/// Create a new `SortedPropList` from a vector of props.
/// The given `props` doesn't need to be sorted.
pub fn new(mut props: Vec<Prop>) -> Self {
props.sort_by(|a, b| Self::cmp_label(&a.label.to_string(), &b.label.to_string()));
Self(props)
}
fn cmp_label(a: &str, b: &str) -> Ordering {
if a == b {
Ordering::Equal
} else if a == Self::CHILDREN_LABEL {
Ordering::Greater
} else if b == Self::CHILDREN_LABEL {
Ordering::Less
} else {
a.cmp(b)
}
}
fn position(&self, key: &str) -> Option<usize> {
self.0
.binary_search_by(|prop| Self::cmp_label(prop.label.to_string().as_str(), key))
.ok()
}
/// Get the first prop with the given key.
pub fn get_by_label(&self, key: &str) -> Option<&Prop> {
self.position(key).and_then(|i| self.0.get(i))
}
/// Pop the first prop with the given key.
pub fn pop(&mut self, key: &str) -> Option<Prop> {
self.position(key).map(|i| self.0.remove(i))
}
/// Pop the prop with the given key and error if there are multiple ones.
pub fn pop_unique(&mut self, key: &str) -> syn::Result<Option<Prop>> {
let prop = self.pop(key);
if prop.is_some() {
if let Some(other_prop) = self.get_by_label(key) {
return Err(syn::Error::new_spanned(
&other_prop.label,
format!("`{}` can only be specified once", key),
));
}
}
Ok(prop)
}
/// Pop the prop with the given key and error if it uses the optional attribute syntax.
pub fn pop_nonoptional(&mut self, key: &str) -> syn::Result<Option<Prop>> {
match self.pop_unique(key) {
Ok(Some(prop)) => {
prop.ensure_not_optional()?;
Ok(Some(prop))
}
res => res,
}
}
/// Turn the props into a vector of `Prop`.
pub fn into_vec(self) -> Vec<Prop> {
self.0
}
/// Iterate over all duplicate props in order of appearance.
fn iter_duplicates(&self) -> impl Iterator<Item = &Prop> {
self.0.windows(2).filter_map(|pair| {
let (a, b) = (&pair[0], &pair[1]);
if a.label == b.label {
Some(b)
} else {
None
}
})
}
/// Remove and return all props for which `filter` returns `true`.
pub fn drain_filter(&mut self, filter: impl FnMut(&Prop) -> bool) -> Self {
let (drained, others) = self.0.drain(..).partition(filter);
self.0 = others;
Self(drained)
}
/// Run the given function for all props and aggregate the errors.
/// If there's at least one error, the result will be `Result::Err`.
pub fn check_all(&self, f: impl FnMut(&Prop) -> syn::Result<()>) -> syn::Result<()> {
crate::join_errors(self.0.iter().map(f).filter_map(Result::err))
}
/// Return an error for all duplicate props.
pub fn check_no_duplicates(&self) -> syn::Result<()> {
crate::join_errors(self.iter_duplicates().map(|prop| {
syn::Error::new_spanned(
&prop.label,
format!(
"`{}` can only be specified once but is given here again",
prop.label
),
)
}))
}
}
impl Parse for SortedPropList {
fn parse(input: ParseStream) -> syn::Result<Self> {
let mut props: Vec<Prop> = Vec::new();
while !input.is_empty() {
props.push(input.parse()?);
}
Ok(Self::new(props))
}
}
impl Deref for SortedPropList {
type Target = [Prop];
fn deref(&self) -> &Self::Target {
&self.0
}
}
#[derive(Default)]
pub struct SpecialProps {
pub node_ref: Option<Prop>,
pub key: Option<Prop>,
}
impl SpecialProps {
const REF_LABEL: &'static str = "ref";
const KEY_LABEL: &'static str = "key";
fn pop_from(props: &mut SortedPropList) -> syn::Result<Self> {
let node_ref = props.pop_nonoptional(Self::REF_LABEL)?;
let key = props.pop_nonoptional(Self::KEY_LABEL)?;
Ok(Self { node_ref, key })
}
pub fn get_slot_mut(&mut self, key: &str) -> Option<&mut Option<Prop>> {
match key {
Self::REF_LABEL => Some(&mut self.node_ref),
Self::KEY_LABEL => Some(&mut self.key),
_ => None,
}
}
fn iter(&self) -> impl Iterator<Item = &Prop> {
self.node_ref.as_ref().into_iter().chain(self.key.as_ref())
}
/// Run the given function for all props and aggregate the errors.
/// If there's at least one error, the result will be `Result::Err`.
pub fn check_all(&self, f: impl FnMut(&Prop) -> syn::Result<()>) -> syn::Result<()> {
crate::join_errors(self.iter().map(f).filter_map(Result::err))
}
}
pub struct Props {
pub special: SpecialProps,
pub prop_list: SortedPropList,
}
impl Parse for Props {
fn parse(input: ParseStream) -> syn::Result<Self> {
Self::try_from(input.parse::<SortedPropList>()?)
}
}
impl Deref for Props {
type Target = SortedPropList;
fn deref(&self) -> &Self::Target {
&self.prop_list
}
}
impl DerefMut for Props {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.prop_list
}
}
impl TryFrom<SortedPropList> for Props {
type Error = syn::Error;
fn try_from(mut prop_list: SortedPropList) -> Result<Self, Self::Error> {
let special = SpecialProps::pop_from(&mut prop_list)?;
Ok(Self { special, prop_list })
}
}

View File

@ -0,0 +1,140 @@
use super::{ComponentProps, Prop, PropPunct, Props, SortedPropList};
use crate::html_tree::HtmlDashedName;
use proc_macro2::TokenStream;
use quote::{quote_spanned, ToTokens};
use std::convert::TryInto;
use syn::{
parse::{Parse, ParseStream},
punctuated::Punctuated,
spanned::Spanned,
token::Brace,
Expr, Token, TypePath,
};
/// Pop from `Punctuated` without leaving it in a state where it has trailing punctuation.
fn pop_last_punctuated<T, P>(punctuated: &mut Punctuated<T, P>) -> Option<T> {
let value = punctuated.pop().map(|pair| pair.into_value());
// remove the 2nd last value and push it right back to remove the trailing punctuation
if let Some(pair) = punctuated.pop() {
punctuated.push_value(pair.into_value());
}
value
}
/// Check if the given type path looks like an associated `Properties` type.
fn is_associated_properties(ty: &TypePath) -> bool {
let mut segments_it = ty.path.segments.iter();
if let Some(seg) = segments_it.next_back() {
// if the last segment is `Properties` ...
if seg.ident == "Properties" {
if let Some(seg) = segments_it.next_back() {
// ... and we can be reasonably sure that the previous segment is a component ...
if !crate::non_capitalized_ascii(&seg.ident.to_string()) {
// ... then we assume that this is an associated type like `Component::Properties`
return true;
}
}
}
}
false
}
struct PropValue {
label: HtmlDashedName,
colon_token: Option<Token![:]>,
value: Expr,
}
impl Parse for PropValue {
fn parse(input: ParseStream) -> syn::Result<Self> {
let label = input.parse()?;
let (colon_token, value) = if input.peek(Token![:]) {
let colon_token = input.parse()?;
let value = input.parse()?;
(Some(colon_token), value)
} else {
let value = syn::parse_quote!(#label);
(None, value)
};
Ok(Self {
label,
colon_token,
value,
})
}
}
impl Into<Prop> for PropValue {
fn into(self) -> Prop {
let Self {
label,
colon_token,
value,
} = self;
Prop {
label,
question_mark: None,
punct: colon_token.map(PropPunct::Colon),
value,
}
}
}
struct PropsExpr {
ty: TypePath,
_brace_token: Brace,
fields: Punctuated<PropValue, Token![,]>,
}
impl Parse for PropsExpr {
fn parse(input: ParseStream) -> syn::Result<Self> {
let mut ty: TypePath = input.parse()?;
// if the type isn't already qualified (`<x as y>`) and it's an associated type (`MyComp::Properties`) ...
if ty.qself.is_none() && is_associated_properties(&ty) {
pop_last_punctuated(&mut ty.path.segments);
// .. transform it into a "qualified-self" type
ty = syn::parse2(quote_spanned! {ty.span()=>
<#ty as ::yew::html::Component>::Properties
})?;
}
let content;
let brace_token = syn::braced!(content in input);
let fields = content.parse_terminated(PropValue::parse)?;
Ok(Self {
ty,
_brace_token: brace_token,
fields,
})
}
}
pub struct PropsMacroInput {
ty: TypePath,
props: ComponentProps,
}
impl Parse for PropsMacroInput {
fn parse(input: ParseStream) -> syn::Result<Self> {
let PropsExpr { ty, fields, .. } = input.parse()?;
let prop_list = SortedPropList::new(fields.into_iter().map(Into::into).collect());
let props: Props = prop_list.try_into()?;
props.special.check_all(|prop| {
let label = &prop.label;
Err(syn::Error::new_spanned(
label,
"special props cannot be specified in the `props!` macro",
))
})?;
Ok(Self {
ty,
props: props.try_into()?,
})
}
}
impl ToTokens for PropsMacroInput {
fn to_tokens(&self, tokens: &mut TokenStream) {
let Self { ty, props } = self;
tokens.extend(props.build_properties_tokens(ty, None::<TokenStream>))
}
}

View File

@ -1,6 +1,5 @@
#![recursion_limit = "128"]
use std::marker::PhantomData;
use yew::html::ChildrenRenderer;
use yew::prelude::*;
#[derive(Clone, Properties, PartialEq)]
@ -89,13 +88,13 @@ fn compile_fail() {
html! { <Child value=1 ref=() with props ref=() /> };
html! { <Child ref=() ref=() value=1 with props /> };
html! { <Child with blah /> };
html! { <Child with props () /> };
html! { <Child value=1 with props /> };
html! { <Child with props value=1 /> };
html! { <Child type=0 /> };
html! { <Child invalid-prop-name=0 /> };
html! { <Child unknown="unknown" /> };
html! { <Child string= /> };
html! { <Child int=1 int=2 int=3 /> };
html! { <Child int=1 string={} /> };
html! { <Child int=1 string=3 /> };
html! { <Child int=1 string={3} /> };
@ -108,12 +107,27 @@ fn compile_fail() {
html! { <Child></Child><Child></Child> };
html! { <Child>{ "Not allowed" }</Child> };
// trying to overwrite `children` on props which don't take any.
html! {
<Child with ChildProperties { string: "hello".to_owned(), int: 5 }>
{ "please error" }
</Child>
};
html! { <ChildContainer /> };
html! { <ChildContainer></ChildContainer> };
html! { <ChildContainer>{ "Not allowed" }</ChildContainer> };
html! { <ChildContainer><></></ChildContainer> };
html! { <ChildContainer><other /></ChildContainer> };
// using `children` as a prop while simultaneously passing children using the syntactic sugar
let children = ChildrenRenderer::new(vec![html_nested! { <Child int=0 /> }]);
html! {
<ChildContainer children=children>
<Child int=1 />
</ChildContainer>
};
html! { <Generic<String>></Generic> };
html! { <Generic<String>></Generic<Vec<String>>> };

View File

@ -1,187 +1,235 @@
error: this opening tag has no corresponding closing tag
--> $DIR/html-component-fail.rs:79:13
--> $DIR/html-component-fail.rs:78:13
|
79 | html! { <Child> };
78 | html! { <Child> };
| ^^^^^^^
error: expected identifier
--> $DIR/html-component-fail.rs:80:22
error: unexpected end of input, expected identifier
--> $DIR/html-component-fail.rs:79:13
|
80 | html! { <Child:: /> };
| ^
79 | html! { <Child:: /> };
| ^^^^^^^^^^^
error: `with` must be followed by an identifier
--> $DIR/html-component-fail.rs:81:20
error: expected expression following this `with`
--> $DIR/html-component-fail.rs:80:20
|
81 | html! { <Child with /> };
80 | html! { <Child with /> };
| ^^^^
error: this prop doesn't have a value
--> $DIR/html-component-fail.rs:82:20
error: `props` doesn't have a value. (hint: set the value to `true` or `false` for boolean attributes)
--> $DIR/html-component-fail.rs:81:20
|
82 | html! { <Child props /> };
81 | html! { <Child props /> };
| ^^^^^
error: this opening tag has no corresponding closing tag
--> $DIR/html-component-fail.rs:83:13
--> $DIR/html-component-fail.rs:82:13
|
83 | html! { <Child with props > };
82 | html! { <Child with props > };
| ^^^^^^^^^^^^^^^^^^^
error: too many refs set
error: `ref` can only be set once
--> $DIR/html-component-fail.rs:83:38
|
83 | html! { <Child with props ref=() ref=() /> };
| ^^^
error: `ref` can only be set once
--> $DIR/html-component-fail.rs:84:38
|
84 | html! { <Child with props ref=() ref=() /> };
84 | html! { <Child with props ref=() ref=() value=1 /> };
| ^^^
error: too many refs set
error: Using the `with props` syntax in combination with named props is not allowed (note: this does not apply to special props like `ref` and `key`)
--> $DIR/html-component-fail.rs:85:38
|
85 | html! { <Child with props ref=() ref=() value=1 /> };
| ^^^
error: Using the `with props` syntax in combination with named props is not allowed (note: this does not apply to the `ref` prop).
--> $DIR/html-component-fail.rs:86:38
|
86 | html! { <Child with props ref=() value=1 ref=() /> };
85 | html! { <Child with props ref=() value=1 ref=() /> };
| ^^^^^
error: Using the `with props` syntax in combination with named props is not allowed (note: this does not apply to the `ref` prop).
--> $DIR/html-component-fail.rs:87:31
error: Using the `with props` syntax in combination with named props is not allowed (note: this does not apply to special props like `ref` and `key`)
--> $DIR/html-component-fail.rs:86:31
|
87 | html! { <Child with props value=1 ref=() ref=() /> };
86 | html! { <Child with props value=1 ref=() ref=() /> };
| ^^^^^
error: Using the `with props` syntax in combination with named props is not allowed (note: this does not apply to the `ref` prop).
--> $DIR/html-component-fail.rs:88:28
error: Using the `with props` syntax in combination with named props is not allowed (note: this does not apply to special props like `ref` and `key`)
--> $DIR/html-component-fail.rs:87:20
|
88 | html! { <Child value=1 with props ref=() ref=() /> };
| ^^^^
87 | html! { <Child value=1 with props ref=() ref=() /> };
| ^^^^^
error: Using the `with props` syntax in combination with named props is not allowed (note: this does not apply to the `ref` prop).
--> $DIR/html-component-fail.rs:89:35
error: Using the `with props` syntax in combination with named props is not allowed (note: this does not apply to special props like `ref` and `key`)
--> $DIR/html-component-fail.rs:88:20
|
89 | html! { <Child value=1 ref=() with props ref=() /> };
| ^^^^
88 | html! { <Child value=1 ref=() with props ref=() /> };
| ^^^^^
error: too many refs set
--> $DIR/html-component-fail.rs:90:27
error: `ref` can only be set once
--> $DIR/html-component-fail.rs:89:27
|
90 | html! { <Child ref=() ref=() value=1 with props /> };
89 | html! { <Child ref=() ref=() value=1 with props /> };
| ^^^
error: unexpected token
error: Using the `with props` syntax in combination with named props is not allowed (note: this does not apply to special props like `ref` and `key`)
--> $DIR/html-component-fail.rs:91:20
|
91 | html! { <Child value=1 with props /> };
| ^^^^^
error: Using the `with props` syntax in combination with named props is not allowed (note: this does not apply to special props like `ref` and `key`)
--> $DIR/html-component-fail.rs:92:31
|
92 | html! { <Child with props () /> };
| ^^
error: Using the `with props` syntax in combination with named props is not allowed (note: this does not apply to the `ref` prop).
--> $DIR/html-component-fail.rs:93:28
|
93 | html! { <Child value=1 with props /> };
| ^^^^
error: Using the `with props` syntax in combination with named props is not allowed (note: this does not apply to the `ref` prop).
--> $DIR/html-component-fail.rs:94:31
|
94 | html! { <Child with props value=1 /> };
92 | html! { <Child with props value=1 /> };
| ^^^^^
error: expected identifier
--> $DIR/html-component-fail.rs:95:20
error: expected identifier, found keyword `type`
--> $DIR/html-component-fail.rs:93:20
|
95 | html! { <Child type=0 /> };
| ^^^^
93 | html! { <Child type=0 /> };
| ^^^^ expected identifier, found keyword
|
help: you can escape reserved keywords to use them as identifiers
|
93 | html! { <Child r#type=0 /> };
| ^^^^^^
error: expected identifier
--> $DIR/html-component-fail.rs:96:20
error: expected a valid Rust identifier
--> $DIR/html-component-fail.rs:94:20
|
96 | html! { <Child invalid-prop-name=0 /> };
94 | html! { <Child invalid-prop-name=0 /> };
| ^^^^^^^^^^^^^^^^^
error: expected an expression following this equals sign
--> $DIR/html-component-fail.rs:98:26
--> $DIR/html-component-fail.rs:96:26
|
98 | html! { <Child string= /> };
96 | html! { <Child string= /> };
| ^
error: too many refs set
--> $DIR/html-component-fail.rs:103:33
error: `int` can only be specified once but is given here again
--> $DIR/html-component-fail.rs:97:26
|
97 | html! { <Child int=1 int=2 int=3 /> };
| ^^^
error: `int` can only be specified once but is given here again
--> $DIR/html-component-fail.rs:97:32
|
97 | html! { <Child int=1 int=2 int=3 /> };
| ^^^
error: `ref` can only be specified once
--> $DIR/html-component-fail.rs:102:26
|
103 | html! { <Child int=1 ref=() ref=() /> };
| ^^^
102 | html! { <Child int=1 ref=() ref=() /> };
| ^^^
error: this closing tag has no corresponding opening tag
--> $DIR/html-component-fail.rs:106:13
--> $DIR/html-component-fail.rs:105:13
|
106 | html! { </Child> };
105 | html! { </Child> };
| ^^^^^^^^
error: this opening tag has no corresponding closing tag
--> $DIR/html-component-fail.rs:107:13
--> $DIR/html-component-fail.rs:106:13
|
107 | html! { <Child><Child></Child> };
106 | html! { <Child><Child></Child> };
| ^^^^^^^
error: only one root html element is allowed (hint: you can wrap multiple html elements in a fragment `<></>`)
--> $DIR/html-component-fail.rs:108:28
--> $DIR/html-component-fail.rs:107:28
|
108 | html! { <Child></Child><Child></Child> };
107 | html! { <Child></Child><Child></Child> };
| ^^^^^^^^^^^^^^^
error: this closing tag has no corresponding opening tag
--> $DIR/html-component-fail.rs:117:30
error: cannot specify the `children` prop when the component already has children
--> $DIR/html-component-fail.rs:126:25
|
117 | html! { <Generic<String>></Generic> };
126 | <ChildContainer children=children>
| ^^^^^^^^
error: this closing tag has no corresponding opening tag
--> $DIR/html-component-fail.rs:131:30
|
131 | html! { <Generic<String>></Generic> };
| ^^^^^^^^^^
error: this closing tag has no corresponding opening tag
--> $DIR/html-component-fail.rs:118:30
--> $DIR/html-component-fail.rs:132:30
|
118 | html! { <Generic<String>></Generic<Vec<String>>> };
132 | html! { <Generic<String>></Generic<Vec<String>>> };
| ^^^^^^^^^^^^^^^^^^^^^^^
error: only one root html element is allowed (hint: you can wrap multiple html elements in a fragment `<></>`)
--> $DIR/html-component-fail.rs:122:9
--> $DIR/html-component-fail.rs:136:9
|
122 | <span>{ 2 }</span>
136 | <span>{ 2 }</span>
| ^^^^^^^^^^^^^^^^^^
error: optional attributes are only supported on HTML tags. Yew components can use `Option<T>` properties to accomplish the same thing.
--> $DIR/html-component-fail.rs:125:28
error: optional attributes are only supported on elements. Components can use `Option<T>` properties to accomplish the same thing.
--> $DIR/html-component-fail.rs:139:28
|
125 | html! { <TestComponent value?="not_supported" /> };
139 | html! { <TestComponent value?="not_supported" /> };
| ^^^^^
error[E0425]: cannot find value `blah` in this scope
--> $DIR/html-component-fail.rs:91:25
--> $DIR/html-component-fail.rs:90:25
|
91 | html! { <Child with blah /> };
90 | html! { <Child with blah /> };
| ^^^^ not found in this scope
error[E0609]: no field `unknown` on type `ChildProperties`
--> $DIR/html-component-fail.rs:97:20
error[E0609]: no field `r#type` on type `ChildProperties`
--> $DIR/html-component-fail.rs:93:20
|
97 | html! { <Child unknown="unknown" /> };
93 | html! { <Child type=0 /> };
| ^^^^ unknown field
|
= note: available fields are: `string`, `int`
error[E0599]: no method named `r#type` found for struct `ChildPropertiesBuilder<ChildPropertiesBuilderStep_missing_required_prop_int>` in the current scope
--> $DIR/html-component-fail.rs:93:20
|
5 | #[derive(Clone, Properties, PartialEq)]
| ---------- method `r#type` not found for this
...
93 | html! { <Child type=0 /> };
| ^^^^ method not found in `ChildPropertiesBuilder<ChildPropertiesBuilderStep_missing_required_prop_int>`
error[E0609]: no field `unknown` on type `ChildProperties`
--> $DIR/html-component-fail.rs:95:20
|
95 | html! { <Child unknown="unknown" /> };
| ^^^^^^^ unknown field
|
= note: available fields are: `string`, `int`
error[E0599]: no method named `unknown` found for struct `ChildPropertiesBuilder<ChildPropertiesBuilderStep_missing_required_prop_int>` in the current scope
--> $DIR/html-component-fail.rs:97:20
--> $DIR/html-component-fail.rs:95:20
|
6 | #[derive(Clone, Properties, PartialEq)]
5 | #[derive(Clone, Properties, PartialEq)]
| ---------- method `unknown` not found for this
...
97 | html! { <Child unknown="unknown" /> };
95 | html! { <Child unknown="unknown" /> };
| ^^^^^^^ method not found in `ChildPropertiesBuilder<ChildPropertiesBuilderStep_missing_required_prop_int>`
error[E0277]: the trait bound `yew::virtual_dom::vcomp::VComp: yew::virtual_dom::Transformer<(), std::string::String>` is not satisfied
--> $DIR/html-component-fail.rs:98:33
|
98 | html! { <Child int=1 string={} /> };
| ^^ the trait `yew::virtual_dom::Transformer<(), std::string::String>` is not implemented for `yew::virtual_dom::vcomp::VComp`
|
= help: the following implementations were found:
<yew::virtual_dom::vcomp::VComp as yew::virtual_dom::Transformer<&'a T, T>>
<yew::virtual_dom::vcomp::VComp as yew::virtual_dom::Transformer<&'a T, std::option::Option<T>>>
<yew::virtual_dom::vcomp::VComp as yew::virtual_dom::Transformer<&'a str, std::option::Option<std::string::String>>>
<yew::virtual_dom::vcomp::VComp as yew::virtual_dom::Transformer<&'a str, std::string::String>>
and 3 others
= note: required by `yew::virtual_dom::Transformer::transform`
error[E0277]: the trait bound `yew::virtual_dom::vcomp::VComp: yew::virtual_dom::Transformer<{integer}, std::string::String>` is not satisfied
--> $DIR/html-component-fail.rs:99:33
|
99 | html! { <Child int=1 string={} /> };
| ^^ the trait `yew::virtual_dom::Transformer<(), std::string::String>` is not implemented for `yew::virtual_dom::vcomp::VComp`
99 | html! { <Child int=1 string=3 /> };
| ^ the trait `yew::virtual_dom::Transformer<{integer}, std::string::String>` is not implemented for `yew::virtual_dom::vcomp::VComp`
|
= help: the following implementations were found:
<yew::virtual_dom::vcomp::VComp as yew::virtual_dom::Transformer<&'a T, T>>
@ -194,21 +242,7 @@ error[E0277]: the trait bound `yew::virtual_dom::vcomp::VComp: yew::virtual_dom:
error[E0277]: the trait bound `yew::virtual_dom::vcomp::VComp: yew::virtual_dom::Transformer<{integer}, std::string::String>` is not satisfied
--> $DIR/html-component-fail.rs:100:33
|
100 | html! { <Child int=1 string=3 /> };
| ^ the trait `yew::virtual_dom::Transformer<{integer}, std::string::String>` is not implemented for `yew::virtual_dom::vcomp::VComp`
|
= help: the following implementations were found:
<yew::virtual_dom::vcomp::VComp as yew::virtual_dom::Transformer<&'a T, T>>
<yew::virtual_dom::vcomp::VComp as yew::virtual_dom::Transformer<&'a T, std::option::Option<T>>>
<yew::virtual_dom::vcomp::VComp as yew::virtual_dom::Transformer<&'a str, std::option::Option<std::string::String>>>
<yew::virtual_dom::vcomp::VComp as yew::virtual_dom::Transformer<&'a str, std::string::String>>
and 3 others
= note: required by `yew::virtual_dom::Transformer::transform`
error[E0277]: the trait bound `yew::virtual_dom::vcomp::VComp: yew::virtual_dom::Transformer<{integer}, std::string::String>` is not satisfied
--> $DIR/html-component-fail.rs:101:33
|
101 | html! { <Child int=1 string={3} /> };
100 | html! { <Child int=1 string={3} /> };
| ^^^ the trait `yew::virtual_dom::Transformer<{integer}, std::string::String>` is not implemented for `yew::virtual_dom::vcomp::VComp`
|
= help: the following implementations were found:
@ -220,15 +254,15 @@ error[E0277]: the trait bound `yew::virtual_dom::vcomp::VComp: yew::virtual_dom:
= note: required by `yew::virtual_dom::Transformer::transform`
error[E0308]: mismatched types
--> $DIR/html-component-fail.rs:102:30
--> $DIR/html-component-fail.rs:101:30
|
102 | html! { <Child int=1 ref=() /> };
101 | html! { <Child int=1 ref=() /> };
| ^^ expected struct `yew::html::NodeRef`, found `()`
error[E0277]: the trait bound `yew::virtual_dom::vcomp::VComp: yew::virtual_dom::Transformer<u32, i32>` is not satisfied
--> $DIR/html-component-fail.rs:104:24
--> $DIR/html-component-fail.rs:103:24
|
104 | html! { <Child int=0u32 /> };
103 | html! { <Child int=0u32 /> };
| ^^^^ the trait `yew::virtual_dom::Transformer<u32, i32>` is not implemented for `yew::virtual_dom::vcomp::VComp`
|
= help: the following implementations were found:
@ -240,34 +274,50 @@ error[E0277]: the trait bound `yew::virtual_dom::vcomp::VComp: yew::virtual_dom:
= note: required by `yew::virtual_dom::Transformer::transform`
error[E0599]: no method named `string` found for struct `ChildPropertiesBuilder<ChildPropertiesBuilderStep_missing_required_prop_int>` in the current scope
--> $DIR/html-component-fail.rs:105:20
--> $DIR/html-component-fail.rs:104:20
|
6 | #[derive(Clone, Properties, PartialEq)]
5 | #[derive(Clone, Properties, PartialEq)]
| ---------- method `string` not found for this
...
105 | html! { <Child string="abc" /> };
104 | html! { <Child string="abc" /> };
| ^^^^^^ method not found in `ChildPropertiesBuilder<ChildPropertiesBuilderStep_missing_required_prop_int>`
|
= help: items from traits can only be used if the trait is implemented and in scope
= note: the following trait defines an item `string`, perhaps you need to implement it:
candidate #1: `proc_macro::bridge::server::Literal`
error[E0599]: no method named `children` found for struct `ChildPropertiesBuilder<ChildPropertiesBuilderStep_missing_required_prop_int>` in the current scope
--> $DIR/html-component-fail.rs:109:14
error[E0609]: no field `children` on type `ChildProperties`
--> $DIR/html-component-fail.rs:108:14
|
6 | #[derive(Clone, Properties, PartialEq)]
108 | html! { <Child>{ "Not allowed" }</Child> };
| ^^^^^ unknown field
|
= note: available fields are: `string`, `int`
error[E0599]: no method named `children` found for struct `ChildPropertiesBuilder<ChildPropertiesBuilderStep_missing_required_prop_int>` in the current scope
--> $DIR/html-component-fail.rs:108:14
|
5 | #[derive(Clone, Properties, PartialEq)]
| ---------- method `children` not found for this
...
109 | html! { <Child>{ "Not allowed" }</Child> };
108 | html! { <Child>{ "Not allowed" }</Child> };
| ^^^^^ method not found in `ChildPropertiesBuilder<ChildPropertiesBuilderStep_missing_required_prop_int>`
error[E0599]: no method named `build` found for struct `ChildContainerPropertiesBuilder<ChildContainerPropertiesBuilderStep_missing_required_prop_children>` in the current scope
--> $DIR/html-component-fail.rs:111:14
error[E0609]: no field `children` on type `ChildProperties`
--> $DIR/html-component-fail.rs:112:10
|
32 | #[derive(Clone, Properties)]
112 | <Child with ChildProperties { string: "hello".to_owned(), int: 5 }>
| ^^^^^ unknown field
|
= note: available fields are: `string`, `int`
error[E0599]: no method named `build` found for struct `ChildContainerPropertiesBuilder<ChildContainerPropertiesBuilderStep_missing_required_prop_children>` in the current scope
--> $DIR/html-component-fail.rs:117:14
|
31 | #[derive(Clone, Properties)]
| ---------- method `build` not found for this
...
111 | html! { <ChildContainer /> };
117 | html! { <ChildContainer /> };
| ^^^^^^^^^^^^^^ method not found in `ChildContainerPropertiesBuilder<ChildContainerPropertiesBuilderStep_missing_required_prop_children>`
|
= help: items from traits can only be used if the trait is implemented and in scope
@ -275,12 +325,12 @@ error[E0599]: no method named `build` found for struct `ChildContainerProperties
candidate #1: `proc_macro::bridge::server::TokenStreamBuilder`
error[E0599]: no method named `build` found for struct `ChildContainerPropertiesBuilder<ChildContainerPropertiesBuilderStep_missing_required_prop_children>` in the current scope
--> $DIR/html-component-fail.rs:112:14
--> $DIR/html-component-fail.rs:118:14
|
32 | #[derive(Clone, Properties)]
31 | #[derive(Clone, Properties)]
| ---------- method `build` not found for this
...
112 | html! { <ChildContainer></ChildContainer> };
118 | html! { <ChildContainer></ChildContainer> };
| ^^^^^^^^^^^^^^ method not found in `ChildContainerPropertiesBuilder<ChildContainerPropertiesBuilderStep_missing_required_prop_children>`
|
= help: items from traits can only be used if the trait is implemented and in scope
@ -288,27 +338,27 @@ error[E0599]: no method named `build` found for struct `ChildContainerProperties
candidate #1: `proc_macro::bridge::server::TokenStreamBuilder`
error[E0277]: the trait bound `yew::virtual_dom::vcomp::VChild<Child>: std::convert::From<yew::virtual_dom::vtext::VText>` is not satisfied
--> $DIR/html-component-fail.rs:113:31
--> $DIR/html-component-fail.rs:119:31
|
113 | html! { <ChildContainer>{ "Not allowed" }</ChildContainer> };
119 | html! { <ChildContainer>{ "Not allowed" }</ChildContainer> };
| ^^^^^^^^^^^^^ the trait `std::convert::From<yew::virtual_dom::vtext::VText>` is not implemented for `yew::virtual_dom::vcomp::VChild<Child>`
|
= note: required because of the requirements on the impl of `std::convert::Into<yew::virtual_dom::vcomp::VChild<Child>>` for `yew::virtual_dom::vtext::VText`
= note: required by `std::convert::Into::into`
error[E0277]: the trait bound `yew::virtual_dom::vcomp::VChild<Child>: std::convert::From<yew::virtual_dom::vnode::VNode>` is not satisfied
--> $DIR/html-component-fail.rs:114:29
--> $DIR/html-component-fail.rs:120:29
|
114 | html! { <ChildContainer><></></ChildContainer> };
120 | html! { <ChildContainer><></></ChildContainer> };
| ^ the trait `std::convert::From<yew::virtual_dom::vnode::VNode>` is not implemented for `yew::virtual_dom::vcomp::VChild<Child>`
|
= note: required because of the requirements on the impl of `std::convert::Into<yew::virtual_dom::vcomp::VChild<Child>>` for `yew::virtual_dom::vnode::VNode`
= note: required by `std::convert::Into::into`
error[E0277]: the trait bound `yew::virtual_dom::vcomp::VChild<Child>: std::convert::From<yew::virtual_dom::vnode::VNode>` is not satisfied
--> $DIR/html-component-fail.rs:115:30
--> $DIR/html-component-fail.rs:121:30
|
115 | html! { <ChildContainer><other /></ChildContainer> };
121 | html! { <ChildContainer><other /></ChildContainer> };
| ^^^^^ the trait `std::convert::From<yew::virtual_dom::vnode::VNode>` is not implemented for `yew::virtual_dom::vcomp::VChild<Child>`
|
= note: required because of the requirements on the impl of `std::convert::Into<yew::virtual_dom::vcomp::VChild<Child>>` for `yew::virtual_dom::vnode::VNode`

View File

@ -183,31 +183,20 @@ mod scoped {
fn compile_pass() {
html! { <Child int=1 /> };
// backwards compat
html! { <Child: int=1 /> };
html! {
<>
<Child int=1 />
<scoped::Child int=1 />
// backwards compat
<Child: int=1 />
<scoped::Child: int=1 />
</>
};
let props = <Child as Component>::Properties::default();
let props2 = <Child as Component>::Properties::default();
let props3 = <Child as Component>::Properties::default();
let props4 = <Child as Component>::Properties::default();
let node_ref = NodeRef::default();
html! {
<>
<Child with props />
<Child: with props2, /> // backwards compat
<Child ref=node_ref.clone() with props3 />
<Child with props4 ref=node_ref />
<Child ref=node_ref.clone() with yew::props!(Child::Properties { int: 5 }) />
<Child with <Child as Component>::Properties::default() ref=node_ref />
</>
};
@ -223,9 +212,6 @@ fn compile_pass() {
<Child opt_str=String::from("child") int=1 />
<Child opt_str=Some("child") int=1 />
<Child opt_str=Some(String::from("child")) int=1 />
// backwards compat
<Child: string="child", int=3, />
</>
};
@ -257,7 +243,7 @@ fn compile_pass() {
<Container int=1></Container>
<Container with props>
<></>
<div>{ "hello world" }</div>
</Container>
<Container int=1>

View File

@ -1,185 +1,185 @@
error: this opening tag has no corresponding closing tag
--> $DIR/html-tag-fail.rs:6:13
--> $DIR/html-element-fail.rs:6:13
|
6 | html! { <div> };
| ^^^^^
error: this opening tag has no corresponding closing tag
--> $DIR/html-tag-fail.rs:7:18
--> $DIR/html-element-fail.rs:7:18
|
7 | html! { <div><div> };
| ^^^^^
error: this closing tag has no corresponding opening tag
--> $DIR/html-tag-fail.rs:8:13
--> $DIR/html-element-fail.rs:8:13
|
8 | html! { </div> };
| ^^^^^^
error: this opening tag has no corresponding closing tag
--> $DIR/html-tag-fail.rs:9:13
--> $DIR/html-element-fail.rs:9:13
|
9 | html! { <div><div></div> };
| ^^^^^
error: only one root html element is allowed (hint: you can wrap multiple html elements in a fragment `<></>`)
--> $DIR/html-tag-fail.rs:10:24
--> $DIR/html-element-fail.rs:10:24
|
10 | html! { <div></div><div></div> };
| ^^^^^^^^^^^
error: this closing tag has no corresponding opening tag
--> $DIR/html-tag-fail.rs:11:18
--> $DIR/html-element-fail.rs:11:18
|
11 | html! { <div></span> };
| ^^^^^^^
error: this closing tag has no corresponding opening tag
--> $DIR/html-tag-fail.rs:12:20
--> $DIR/html-element-fail.rs:12:20
|
12 | html! { <tag-a></tag-b> };
| ^^^^^^^^
error: this closing tag has no corresponding opening tag
--> $DIR/html-tag-fail.rs:13:18
--> $DIR/html-element-fail.rs:13:18
|
13 | html! { <div></span></div> };
| ^^^^^^^
error: only one root html element is allowed (hint: you can wrap multiple html elements in a fragment `<></>`)
--> $DIR/html-tag-fail.rs:14:20
--> $DIR/html-element-fail.rs:14:20
|
14 | html! { <img /></img> };
| ^^^^^^
error: expected a valid html element
--> $DIR/html-tag-fail.rs:15:18
--> $DIR/html-element-fail.rs:15:18
|
15 | html! { <div>Invalid</div> };
| ^^^^^^^
error: the attribute `attr` can only be specified once
--> $DIR/html-tag-fail.rs:17:27
error: `attr` can only be specified once but is given here again
--> $DIR/html-element-fail.rs:17:27
|
17 | html! { <input attr=1 attr=2 /> };
| ^^^^
error: the attribute `value` can only be specified once
--> $DIR/html-tag-fail.rs:18:32
error: `value` can only be specified once but is given here again
--> $DIR/html-element-fail.rs:18:32
|
18 | html! { <input value="123" value="456" /> };
| ^^^^^
error: the attribute `kind` can only be specified once
--> $DIR/html-tag-fail.rs:19:36
error: `kind` can only be specified once but is given here again
--> $DIR/html-element-fail.rs:19:36
|
19 | html! { <input kind="checkbox" kind="submit" /> };
| ^^^^
error: the attribute `checked` can only be specified once
--> $DIR/html-tag-fail.rs:20:33
error: `checked` can only be specified once but is given here again
--> $DIR/html-element-fail.rs:20:33
|
20 | html! { <input checked=true checked=false /> };
| ^^^^^^^
error: the attribute `disabled` can only be specified once
--> $DIR/html-tag-fail.rs:21:34
error: `disabled` can only be specified once but is given here again
--> $DIR/html-element-fail.rs:21:34
|
21 | html! { <input disabled=true disabled=false /> };
| ^^^^^^^^
error: the attribute `selected` can only be specified once
--> $DIR/html-tag-fail.rs:22:35
error: `selected` can only be specified once but is given here again
--> $DIR/html-element-fail.rs:22:35
|
22 | html! { <option selected=true selected=false /> };
| ^^^^^^^^
error: the attribute `class` can only be specified once
--> $DIR/html-tag-fail.rs:23:32
error: `class` can only be specified once but is given here again
--> $DIR/html-element-fail.rs:23:32
|
23 | html! { <div class="first" class="second" /> };
| ^^^^^
error: the attribute `ref` can only be specified once
--> $DIR/html-tag-fail.rs:38:27
error: `ref` can only be specified once
--> $DIR/html-element-fail.rs:38:20
|
38 | html! { <input ref=() ref=() /> };
| ^^^
| ^^^
error: the tag `<input>` is a void element and cannot have children (hint: rewrite this as `<input/>`)
--> $DIR/html-tag-fail.rs:40:13
--> $DIR/html-element-fail.rs:40:13
|
40 | html! { <input type="text"></input> };
| ^^^^^^^^^^^^^^^^^^^
error: the tag `<iNpUt>` is a void element and cannot have children (hint: rewrite this as `<iNpUt/>`)
--> $DIR/html-tag-fail.rs:41:13
--> $DIR/html-element-fail.rs:41:13
|
41 | html! { <iNpUt type="text"></iNpUt> };
| ^^^^^^^^^^^^^^^^^^^
error: this dynamic tag is missing an expression block defining its value
--> $DIR/html-tag-fail.rs:43:14
--> $DIR/html-element-fail.rs:43:14
|
43 | html! { <@></@> };
| ^
error: dynamic closing tags must not have a body (hint: replace it with just `</@>`)
--> $DIR/html-tag-fail.rs:44:27
--> $DIR/html-element-fail.rs:44:27
|
44 | html! { <@{"test"}></@{"test"}> };
| ^^^^^^^^
error: this dynamic tag is missing an expression block defining its value
--> $DIR/html-tag-fail.rs:46:14
--> $DIR/html-element-fail.rs:46:14
|
46 | html! { <@/> };
| ^
error: boolean attributes don't support being used as an optional attribute (hint: a value of false results in the attribute not being set)
--> $DIR/html-tag-fail.rs:50:20
--> $DIR/html-element-fail.rs:50:20
|
50 | html! { <input disabled?=Some(true) /> };
| ^^^^^^^^
error: the `checked` attribute does not support being used as an optional attribute
--> $DIR/html-tag-fail.rs:58:20
error: `checked` does not support being used as an optional attribute
--> $DIR/html-element-fail.rs:58:20
|
58 | html! { <input checked?=Some(false) /> };
| ^^^^^^^
| ^^^^^^^^^
error: the `class` attribute does not support being used as an optional attribute
--> $DIR/html-tag-fail.rs:59:20
error: `class` does not support being used as an optional attribute
--> $DIR/html-element-fail.rs:59:20
|
59 | html! { <input class?=() /> };
| ^^^^^
| ^^^^^^^
error: the `ref` attribute does not support being used as an optional attribute
--> $DIR/html-tag-fail.rs:60:20
error: `ref` does not support being used as an optional attribute
--> $DIR/html-element-fail.rs:60:20
|
60 | html! { <input ref?=() /> };
| ^^^
| ^^^^^
error[E0308]: mismatched types
--> $DIR/html-tag-fail.rs:25:28
--> $DIR/html-element-fail.rs:25:28
|
25 | html! { <input checked=1 /> };
| ^ expected `bool`, found integer
error[E0308]: mismatched types
--> $DIR/html-tag-fail.rs:26:29
--> $DIR/html-element-fail.rs:26:29
|
26 | html! { <input disabled=1 /> };
| ^ expected `bool`, found integer
error[E0308]: mismatched types
--> $DIR/html-tag-fail.rs:27:30
--> $DIR/html-element-fail.rs:27:30
|
27 | html! { <option selected=1 /> };
| ^ expected `bool`, found integer
error[E0277]: `()` doesn't implement `std::fmt::Display`
--> $DIR/html-tag-fail.rs:28:25
--> $DIR/html-element-fail.rs:28:25
|
28 | html! { <input type=() /> };
| ^^ `()` cannot be formatted with the default formatter
@ -190,7 +190,7 @@ error[E0277]: `()` doesn't implement `std::fmt::Display`
= note: required by `std::string::ToString::to_string`
error[E0277]: `()` doesn't implement `std::fmt::Display`
--> $DIR/html-tag-fail.rs:29:26
--> $DIR/html-element-fail.rs:29:26
|
29 | html! { <input value=() /> };
| ^^ `()` cannot be formatted with the default formatter
@ -200,7 +200,7 @@ error[E0277]: `()` doesn't implement `std::fmt::Display`
= note: required because of the requirements on the impl of `std::string::ToString` for `()`
error[E0277]: `()` doesn't implement `std::fmt::Display`
--> $DIR/html-tag-fail.rs:30:21
--> $DIR/html-element-fail.rs:30:21
|
30 | html! { <a href=() /> };
| ^^ `()` cannot be formatted with the default formatter
@ -211,7 +211,7 @@ error[E0277]: `()` doesn't implement `std::fmt::Display`
= note: required by `std::string::ToString::to_string`
error[E0308]: mismatched types
--> $DIR/html-tag-fail.rs:32:28
--> $DIR/html-element-fail.rs:32:28
|
32 | html! { <input onclick=1 /> };
| ^ expected enum `yew::callback::Callback`, found integer
@ -220,7 +220,7 @@ error[E0308]: mismatched types
found type `{integer}`
error[E0308]: mismatched types
--> $DIR/html-tag-fail.rs:33:28
--> $DIR/html-element-fail.rs:33:28
|
33 | html! { <input onclick=Callback::from(|a: String| ()) /> };
| ^^^^^^^^ expected struct `web_sys::features::gen_MouseEvent::MouseEvent`, found struct `std::string::String`
@ -229,7 +229,7 @@ error[E0308]: mismatched types
found enum `yew::callback::Callback<std::string::String>`
error[E0277]: `NotToString` doesn't implement `std::fmt::Display`
--> $DIR/html-tag-fail.rs:35:27
--> $DIR/html-element-fail.rs:35:27
|
35 | html! { <input string=NotToString /> };
| ^^^^^^^^^^^ `NotToString` cannot be formatted with the default formatter
@ -240,13 +240,13 @@ error[E0277]: `NotToString` doesn't implement `std::fmt::Display`
= note: required by `std::string::ToString::to_string`
error[E0308]: mismatched types
--> $DIR/html-tag-fail.rs:37:24
--> $DIR/html-element-fail.rs:37:24
|
37 | html! { <input ref=() /> };
| ^^ expected struct `yew::html::NodeRef`, found `()`
error[E0277]: the trait bound `std::borrow::Cow<'static, str>: std::convert::From<{integer}>` is not satisfied
--> $DIR/html-tag-fail.rs:45:15
--> $DIR/html-element-fail.rs:45:15
|
45 | html! { <@{55}></@> };
| ^^^^ the trait `std::convert::From<{integer}>` is not implemented for `std::borrow::Cow<'static, str>`
@ -261,7 +261,7 @@ error[E0277]: the trait bound `std::borrow::Cow<'static, str>: std::convert::Fro
= note: required by `std::convert::Into::into`
error[E0308]: mismatched types
--> $DIR/html-tag-fail.rs:48:23
--> $DIR/html-element-fail.rs:48:23
|
48 | html! { <a media?="media" /> };
| ^^^^^^^
@ -273,7 +273,7 @@ error[E0308]: mismatched types
found reference `&'static str`
error[E0277]: `NotToString` doesn't implement `std::fmt::Display`
--> $DIR/html-tag-fail.rs:49:23
--> $DIR/html-element-fail.rs:49:23
|
49 | html! { <a media?=Some(NotToString) /> };
| ^^^^ `NotToString` cannot be formatted with the default formatter
@ -284,7 +284,7 @@ error[E0277]: `NotToString` doesn't implement `std::fmt::Display`
= note: required by `std::string::ToString::to_string`
error[E0308]: mismatched types
--> $DIR/html-tag-fail.rs:51:26
--> $DIR/html-element-fail.rs:51:26
|
51 | html! { <input type?="kind" /> };
| ^^^^^^
@ -296,7 +296,7 @@ error[E0308]: mismatched types
found reference `&'static str`
error[E0277]: `NotToString` doesn't implement `std::fmt::Display`
--> $DIR/html-tag-fail.rs:52:26
--> $DIR/html-element-fail.rs:52:26
|
52 | html! { <input type?=Some(NotToString) /> };
| ^^^^ `NotToString` cannot be formatted with the default formatter
@ -307,7 +307,7 @@ error[E0277]: `NotToString` doesn't implement `std::fmt::Display`
= note: required by `std::string::ToString::to_string`
error[E0308]: mismatched types
--> $DIR/html-tag-fail.rs:53:24
--> $DIR/html-element-fail.rs:53:24
|
53 | html! { <li value?="value" /> };
| ^^^^^^^
@ -319,7 +319,7 @@ error[E0308]: mismatched types
found reference `&'static str`
error[E0277]: `NotToString` doesn't implement `std::fmt::Display`
--> $DIR/html-tag-fail.rs:54:24
--> $DIR/html-element-fail.rs:54:24
|
54 | html! { <li value?=Some(NotToString) /> };
| ^^^^ `NotToString` cannot be formatted with the default formatter
@ -330,7 +330,7 @@ error[E0277]: `NotToString` doesn't implement `std::fmt::Display`
= note: required by `std::string::ToString::to_string`
error[E0308]: mismatched types
--> $DIR/html-tag-fail.rs:56:22
--> $DIR/html-element-fail.rs:56:22
|
56 | html! { <a href?="href" /> };
| ^^^^^^
@ -342,7 +342,7 @@ error[E0308]: mismatched types
found reference `&'static str`
error[E0277]: `NotToString` doesn't implement `std::fmt::Display`
--> $DIR/html-tag-fail.rs:57:22
--> $DIR/html-element-fail.rs:57:22
|
57 | html! { <a href?=Some(NotToString) /> };
| ^^^^ `NotToString` cannot be formatted with the default formatter
@ -353,7 +353,7 @@ error[E0277]: `NotToString` doesn't implement `std::fmt::Display`
= note: required by `std::string::ToString::to_string`
error[E0308]: mismatched types
--> $DIR/html-tag-fail.rs:61:20
--> $DIR/html-element-fail.rs:61:20
|
61 | html! { <input onfocus?=Some(5) /> };
| ^^^^^^^ expected enum `yew::callback::Callback`, found integer
@ -362,7 +362,7 @@ error[E0308]: mismatched types
found type `{integer}`
error[E0308]: mismatched types
--> $DIR/html-tag-fail.rs:62:29
--> $DIR/html-element-fail.rs:62:29
|
62 | html! { <input onfocus?=Callback::from(|_| ()) /> };
| ^^^^^^^^^^^^^^^^^^^^^^

View File

@ -12,7 +12,7 @@ fn compile_pass() {
<div>
<div data-key="abc"></div>
<div ref=parent_ref class="parent">
<span class="child", value="anything",></span>
<span class="child" value="anything"></span>
<label for="first-name">{"First Name"}</label>
<input type="text" id="first-name" value="placeholder" />
<input type="checkbox" checked=true />
@ -38,7 +38,7 @@ fn compile_pass() {
</defs>
</svg>
<img class=("avatar", "hidden") src="http://pic.com" />
<img class="avatar hidden", />
<img class="avatar hidden" />
<button onclick=&onclick onclick=onclick />
<a href="http://google.com" />
<custom-tag-a>

View File

@ -64,8 +64,8 @@ error: fragments only accept the `key` prop
15 | html! { <some_attr="test"></> };
| ^^^^^^^^^
error: the `key` attribute does not support being used as an optional attribute
error: `key` does not support being used as an optional attribute
--> $DIR/html-list-fail.rs:17:14
|
17 | html! { <key?=None></> };
| ^^^
| ^^^^^

View File

@ -1,29 +0,0 @@
use yew::prelude::*;
#[derive(Clone, Properties, PartialEq)]
pub struct TestProperties {
pub string: String,
pub int: i32,
}
pub struct TestComponent;
impl Component for TestComponent {
type Message = ();
type Properties = TestProperties;
fn create(_: Self::Properties, _: ComponentLink<Self>) -> Self {
TestComponent
}
fn update(&mut self, _: Self::Message) -> ShouldRender {
unimplemented!()
}
fn change(&mut self, _: Self::Properties) -> ShouldRender {
unimplemented!()
}
fn view(&self) -> Html {
unimplemented!()
}
}

View File

@ -21,8 +21,8 @@ fn tests() {
t.pass("tests/macro/html-node-pass.rs");
t.compile_fail("tests/macro/html-node-fail.rs");
t.pass("tests/macro/html-tag-pass.rs");
t.compile_fail("tests/macro/html-tag-fail.rs");
t.pass("tests/macro/html-element-pass.rs");
t.compile_fail("tests/macro/html-element-fail.rs");
}
#[test]

View File

@ -0,0 +1,18 @@
use yew::prelude::*;
#[derive(Clone, Properties)]
struct Props {
a: usize,
}
fn compile_fail() {
yew::props!(Props { ref: NodeRef::default(), key: "key" });
yew::props!(Props { a: 5, fail: 10 });
let props = yew::props!(Props { a: 1 });
yew::props!(Props { a: 1, ..props });
yew::props!(Props { does_not_exist });
}
fn main() {}

View File

@ -0,0 +1,57 @@
error: special props cannot be specified in the `props!` macro
--> $DIR/props-fail.rs:9:25
|
9 | yew::props!(Props { ref: NodeRef::default(), key: "key" });
| ^^^
error: special props cannot be specified in the `props!` macro
--> $DIR/props-fail.rs:9:50
|
9 | yew::props!(Props { ref: NodeRef::default(), key: "key" });
| ^^^
error: expected ident
--> $DIR/props-fail.rs:13:31
|
13 | yew::props!(Props { a: 1, ..props });
| ^^
error[E0425]: cannot find value `does_not_exist` in this scope
--> $DIR/props-fail.rs:15:25
|
15 | yew::props!(Props { does_not_exist });
| ^^^^^^^^^^^^^^ not found in this scope
error[E0609]: no field `fail` on type `Props`
--> $DIR/props-fail.rs:10:31
|
10 | yew::props!(Props { a: 5, fail: 10 });
| ^^^^ unknown field
|
= note: available fields are: `a`
error[E0599]: no method named `fail` found for struct `PropsBuilder<PropsBuilderStep_build>` in the current scope
--> $DIR/props-fail.rs:10:31
|
3 | #[derive(Clone, Properties)]
| ---------- method `fail` not found for this
...
10 | yew::props!(Props { a: 5, fail: 10 });
| ^^^^ method not found in `PropsBuilder<PropsBuilderStep_build>`
error[E0609]: no field `does_not_exist` on type `Props`
--> $DIR/props-fail.rs:15:25
|
15 | yew::props!(Props { does_not_exist });
| ^^^^^^^^^^^^^^ unknown field
|
= note: available fields are: `a`
error[E0599]: no method named `does_not_exist` found for struct `PropsBuilder<PropsBuilderStep_missing_required_prop_a>` in the current scope
--> $DIR/props-fail.rs:15:25
|
3 | #[derive(Clone, Properties)]
| ---------- method `does_not_exist` not found for this
...
15 | yew::props!(Props { does_not_exist });
| ^^^^^^^^^^^^^^ method not found in `PropsBuilder<PropsBuilderStep_missing_required_prop_a>`

View File

@ -0,0 +1,16 @@
use yew::prelude::*;
#[derive(Clone, Properties)]
struct Props {
a: usize,
#[prop_or_default]
b: usize,
}
fn compile_pass() {
yew::props!(Props { a: 5 });
let (a, b) = (3, 5);
yew::props!(Props { a, b });
}
fn main() {}

View File

@ -0,0 +1,43 @@
use yew::prelude::*;
#[derive(Clone, Properties)]
struct Props {}
struct MyComp;
impl Component for MyComp {
type Message = ();
type Properties = Props;
fn create(_props: Self::Properties, _link: ComponentLink<Self>) -> Self {
unimplemented!()
}
fn update(&mut self, _msg: Self::Message) -> ShouldRender {
unimplemented!()
}
fn change(&mut self, _props: Self::Properties) -> ShouldRender {
unimplemented!()
}
fn view(&self) -> Html {
unimplemented!()
}
}
trait NotAComponent {
type Properties;
}
struct MyNotAComponent;
impl NotAComponent for MyNotAComponent {
type Properties = ();
}
fn compile_fail() {
yew::props!(Vec<_> {});
yew::props!(MyComp {});
yew::props!(MyNotAComponent::Properties {});
}
fn main() {}

View File

@ -0,0 +1,17 @@
error[E0277]: the trait bound `std::vec::Vec<_>: yew::html::Properties` is not satisfied
--> $DIR/resolve-prop-fail.rs:38:17
|
38 | yew::props!(Vec<_> {});
| ^^^ the trait `yew::html::Properties` is not implemented for `std::vec::Vec<_>`
error[E0277]: the trait bound `MyComp: yew::html::Properties` is not satisfied
--> $DIR/resolve-prop-fail.rs:39:17
|
39 | yew::props!(MyComp {});
| ^^^^^^ the trait `yew::html::Properties` is not implemented for `MyComp`
error[E0277]: the trait bound `MyNotAComponent: yew::html::Component` is not satisfied
--> $DIR/resolve-prop-fail.rs:40:17
|
40 | yew::props!(MyNotAComponent::Properties {});
| ^^^^^^^^^^^^^^^ the trait `yew::html::Component` is not implemented for `MyNotAComponent`

View File

@ -0,0 +1,38 @@
use yew::prelude::*;
#[derive(Clone, Properties)]
struct Props {
n: i32,
}
struct MyComp;
impl Component for MyComp {
type Message = ();
type Properties = Props;
fn create(_props: Self::Properties, _link: ComponentLink<Self>) -> Self {
unimplemented!()
}
fn update(&mut self, _msg: Self::Message) -> ShouldRender {
unimplemented!()
}
fn change(&mut self, _props: Self::Properties) -> ShouldRender {
unimplemented!()
}
fn view(&self) -> Html {
unimplemented!()
}
}
fn compile_pass() {
yew::props!(Props { n: 1 });
yew::props!(self::Props { n: 1 });
yew::props!(MyComp::Properties { n: 2 });
yew::props!(self::MyComp::Properties { n: 3 });
yew::props!(<MyComp as Component>::Properties { n: 5 });
}
fn main() {}

View File

@ -0,0 +1,7 @@
#[allow(dead_code)]
#[rustversion::attr(stable(1.45), test)]
fn tests() {
let t = trybuild::TestCases::new();
t.pass("tests/props_macro/*-pass.rs");
t.compile_fail("tests/props_macro/*-fail.rs");
}

View File

@ -59,9 +59,9 @@ impl<SW: Switch + Clone + 'static, STATE: RouterState> Component for RouterButto
});
html! {
<button
class=self.props.classes.clone(),
onclick=cb,
disabled=self.props.disabled,
class=self.props.classes.clone()
onclick=cb
disabled=self.props.disabled
>
{
#[allow(deprecated)]

View File

@ -72,10 +72,10 @@ impl<SW: Switch + Clone + 'static, STATE: RouterState> Component for RouterAncho
html! {
<a
class=self.props.classes.clone(),
onclick=cb,
disabled=self.props.disabled,
href=target,
class=self.props.classes.clone()
onclick=cb
disabled=self.props.disabled
href=target
>
{
#[allow(deprecated)]

View File

@ -9,6 +9,7 @@ mod scope;
pub use listener::*;
pub use scope::{AnyScope, Scope, SendAsMessage};
pub(crate) use scope::{ComponentUpdate, Scoped};
pub use yew_macro::Properties;
use crate::callback::Callback;
use crate::virtual_dom::{VChild, VNode};
@ -318,7 +319,7 @@ where
}
/// Render children components and return `Iterator`
pub fn iter<'a>(&'a self) -> impl Iterator<Item = T> + 'a {
pub fn iter(&self) -> impl Iterator<Item = T> + '_ {
// clone each child lazily.
// This way `self.iter().next()` only has to clone a single node.
self.children.iter().cloned()

View File

@ -36,19 +36,20 @@
//! ```rust
//! use yew::prelude::*;
//!
//! enum Msg {
//! AddOne,
//! }
//!
//! struct Model {
//! link: ComponentLink<Self>,
//! value: i64,
//! }
//!
//! enum Msg {
//! AddOne,
//! }
//!
//! impl Component for Model {
//! type Message = Msg;
//! type Properties = ();
//! fn create(_: Self::Properties, link: ComponentLink<Self>) -> Self {
//!
//! fn create(_props: Self::Properties, link: ComponentLink<Self>) -> Self {
//! Self {
//! link,
//! value: 0,
@ -57,12 +58,14 @@
//!
//! fn update(&mut self, msg: Self::Message) -> ShouldRender {
//! match msg {
//! Msg::AddOne => self.value += 1
//! Msg::AddOne => {
//! self.value += 1;
//! true
//! }
//! }
//! true
//! }
//!
//! fn change(&mut self, _: Self::Properties) -> ShouldRender {
//! fn change(&mut self, _props: Self::Properties) -> ShouldRender {
//! false
//! }
//!
@ -78,8 +81,7 @@
//!
//!# fn dont_execute() {
//! fn main() {
//! yew::initialize();
//! App::<Model>::new().mount_to_body();
//! yew::start_app::<Model>();
//! }
//!# }
//! ```
@ -194,11 +196,65 @@ pub use yew_macro::html;
/// [`ChildrenRenderer<ListItem>`]: ./html/struct.ChildrenRenderer.html
pub use yew_macro::html_nested;
/// Build [`Properties`] outside of the [`html!`] macro.
///
/// It's already possible to create properties like normal Rust structs
/// but if there are lots of optional props the end result is often needlessly verbose.
/// This macro allows you to build properties the same way the [`html!`] macro does.
///
/// The macro doesn't support special props like `ref` and `key`, they need to be set in the [`html!`] macro.
///
/// You can read more about `Properties` in the [Yew Docs].
///
/// # Example
///
/// ```
/// # use yew::prelude::*;
/// use std::borrow::Cow;
///
/// #[derive(Clone, Properties)]
/// struct Props {
/// #[prop_or_default]
/// id: usize,
/// name: Cow<'static, str>,
/// }
///
/// struct Model(Props);
/// impl Component for Model {
/// # type Message = ();
/// type Properties = Props;
/// // ...
/// # fn create(_props: Self::Properties, _link: ComponentLink<Self>) -> Self { unimplemented!() }
/// # fn update(&mut self, _msg: Self::Message) -> ShouldRender { unimplemented!() }
/// # fn change(&mut self, _props: Self::Properties) -> ShouldRender { unimplemented!() }
/// # fn view(&self) -> Html { unimplemented!() }
/// }
///
/// # fn foo() -> Html {
/// // You can build props directly ...
/// let props = yew::props!(Props { name: Cow::from("Minka") });
/// # assert_eq!(props.name, "Minka");
/// // ... or build the associated properties of a component
/// let props = yew::props!(Model::Properties { id: 2, name: Cow::from("Lemmy") });
/// # assert_eq!(props.id, 2);
///
/// // Use the `with props` syntax to create a component with the props.
/// html! {
/// <Model key=1 with props />
/// }
/// # }
/// ```
///
/// [`html!`]: ./macro.html.html
/// [`Properties`]: ./html/trait.Properties.html
/// [yew docs]: https://yew.rs/docs/en/concepts/components/properties
pub use yew_macro::props;
/// This module contains macros which implements html! macro and JSX-like templates
pub mod macros {
pub use crate::html;
pub use crate::html_nested;
pub use yew_macro::Properties;
pub use crate::props;
}
pub mod app;
@ -301,7 +357,7 @@ pub mod prelude {
Children, ChildrenWithProps, Component, ComponentLink, Html, NodeRef, Properties,
ShouldRender,
};
pub use crate::macros::*;
pub use crate::macros::{html, html_nested};
pub use crate::virtual_dom::Classes;
/// Prelude module for creating worker.

View File

@ -284,8 +284,9 @@ impl<COMP: Component> fmt::Debug for VChild<COMP> {
#[cfg(test)]
mod tests {
use super::*;
use crate::macros::Properties;
use crate::{html, Children, Component, ComponentLink, Html, NodeRef, ShouldRender};
use crate::{
html, Children, Component, ComponentLink, Html, NodeRef, Properties, ShouldRender,
};
use cfg_match::cfg_match;
#[cfg(feature = "std_web")]

View File

@ -908,7 +908,7 @@ mod tests {
#[test]
fn keeps_order_of_classes() {
let a = html! {
<div class=vec!["class-1", "class-2", "class-3"],></div>
<div class=vec!["class-1", "class-2", "class-3"]></div>
};
if let VNode::VTag(vtag) = a {

View File

@ -68,7 +68,7 @@ pub struct ReadOnly<S> {
impl<S> ReadOnly<S> {
/// Allow only immutable borrows to the underlying data
pub fn borrow<'a>(&'a self) -> impl Deref<Target = S> + 'a {
pub fn borrow(&self) -> impl Deref<Target = S> + '_ {
self.state.borrow()
}
}