#[web_component]Expand description
Proc macro to create the web component glue
§Examples
§Greeting example
use dioxus::logger::tracing::Level;
use dioxus::{logger, prelude::*};
use dioxus_web_component::{web_component, InjectedStyle};
use wasm_bindgen::prelude::*;
/// Install (register) the web component
#[wasm_bindgen(start)]
pub fn register() {
let _ = logger::init(Level::INFO);
register_greetings();
}
#[web_component(tag = "plop-greeting", style = InjectedStyle::css(":host { /* ... */ }") )]
fn Greetings(
// The name can be set as an attribute of the plop-greeting HTML element
#[attribute]
#[property]
name: String,
) -> Element {
rsx! {
p { "Hello {name}!" }
}
}See full example
§Counter example
use dioxus::logger::tracing::Level;
use dioxus::{logger, prelude::*};
use dioxus_web_component::{web_component, InjectedStyle};
use wasm_bindgen::prelude::*;
/// Install (register) the web component
#[wasm_bindgen(start)]
pub fn register() {
let _ = logger::init(Level::INFO);
// The register counter is generated by the `#[web_component(...)]` macro
register_counter();
}
/// The Dioxus component
#[web_component(tag = "plop-counter", style = InjectedStyle::stylesheet("./style.css"))]
fn Counter(
// The label is only available with a property
#[property] label: String,
// This component can trigger a custom 'count' event
on_count: EventHandler<i32>,
) -> Element {
let mut counter = use_signal(|| 0);
rsx! {
span { "{label}" }
button {
onclick: move |_| {
counter += 1;
on_count(counter());
},
"+"
}
output { "{counter}" }
}
}See full example
§Macro attributes
§Tag
The custom element tag is built from the component name.
By default, the tag is the kebab-case version of the name.
For example, MyWebComponent becomes my-web-component.
You can change the default behavior with the tag attribute.
use dioxus::prelude::*;
use dioxus_web_component::{web_component, DioxusWebComponent};
#[web_component(tag = "plop-component")]
fn MyWebComponent(
// ...
) -> Element { todo!() }<!-- in the body -->
<plop-component></plop-component>ℹ️ INFO: the custom element tag name has constraints. The macro checks the validity of the tag for you. See MDN - Valid custom element names
§Style
You can provide the web component style with the style attribute.
use dioxus::prelude::*;
use dioxus_web_component::{web_component, InjectedStyle};
#[web_component(
tag = "plop-greeting",
style = InjectedStyle::css(include_str!("style.css"))
)]
fn Greeting(
// ...
) -> Element {
todo!()
}The dioxus_web_component::InjectedStyle could be raw CSS included in
an HTML <style>...</style> element, or a link to an external stylesheet,
or a list of InjectedStyle styles.
⚠️ WARNING: the web component is wrapped into an HTML div with the dioxus CSS class.
§Component fields annotations
Every parameter of your component should be an attribute, a property, or an event. Note that a parameter could be both an attribute and a property.
The proc macro tries to detect the kind of parameter by looking at its type.
If the type starts by EventHandler it is expected to be an event.
But, this kind of detection is not fully reliable, so you might need to add an annotation
to correct this behavior.
The annotations are also required if you need to customize the behavior.
§Attributes
Attributes are like the href of an <a> HTML element.
You can enforce the parameter to be an attribute with the #[attribute] annotation.
When the attribute value changes the dioxus component will be rendered.
The HTML value of an attribute is a String, so you should be able
to parse that string into the target type.
name
The attribute name is by default the kebab-case of the parameter name.
You can choose another name with #[attribute(name = "my-custom-name")].
option
The attribute could be optional or not.
The proc macro tries to detect it automatically with the type name.
However the detection is not fully reliable, so you can use the #[attribute(option = true)]
to fix the detection if necessary.
initial
Attributes require to have an initial value. This value is used when no HTML attribute is provided, or if the attribute is removed.
By default, we expect the attribute type to implement [std::default::Default].
If it’s not the case, or if you want to use another value for your attribute you
can provide your default expression with #[attribute(initial = String::from("World"))].
Note that Option<T> implements Default with the None value
even if T does not implement itself Default.
parse
HTML attributes are strings and optional, so we need to convert the attribute value into the component parameter type.
The proc macro uses the std::str::parse method. That means the target type
needs to implement the std::str::FromStr trait.
In case of an error, the initial value (see below) is used.
If you want to change this behavior, you can provide your parsing expression.
If the parameter type is optional, the parse expression is used in this code:
let value = new_value.and_then(#parse);.
If the type is NOT optional, the code looks like let value = new_value.and_then(#parse).unwrap_or_else(|| #initial);.
The expected type for the parsing expression is FnOnce(String) -> Option<T>.
The default expression is |value| value.parse().ok().
For example, if you have a parameter required of type bool and you want the value to be true
if the attribute is present whatever the content of the attribute, you could use #[attribute(parse = |s| !s.is_empty() )].
§Property
On the Rust side of the code, properties work like attributes. The property is not accessible with pure HTML, you need Javascript to get/set the property.
Instead of the String representation, you need to be able to convert the Rust type into a
Javascript type (here a wasm_bindgen::JsValue).
For the setter, you need the opposite conversion.
name
The attribute name is by default the camelCase of the parameter name.
You can choose another name with #[property(name = "valueAsDate")].
readonly
If true, it avoids setting the property from the javascript side.
By default getter and setter are generated.
initial
Properties require to have an initial value. This value is used when the component is initialized.
By default, we expect the property type to implement [std::default::Default].
If it’s not the case, or if you want to use another value for your property you
can provide your default expression with #[property(initial = String::from("World"))].
- Conversion with
try_into_js,try_from_js
For the getter, the property value should be converted to a wasm_bindgen::JsValue.
By default, we use the std::convert::TryInto implementation.
Note that there are many ways to implement TryInto<JsValue>,
for example with impl TryFrom<T> for JsValue or even impl From<T> for JsValue.
See Rust TryInto.
You can also use wasm-bindgen to generate the conversion of a struct.
You may required to provide your custom conversion into the JsValue
with the try_into_js attribute (orphan rule).
The expected type for the parsing expression is FnOnce(T) -> Result<JsValue, _>.
Not that we do not care about the error type because
the error case is ignored and returns undefined.
The default expression is |value| value.try_into().
For the setter, the property value should be converted from a wasm_bindgen::JsValue.
By default, we use the std::convert::TryInto implementation.
Note that there are many ways to implement TryInto<T>,
for example with impl TryFrom<JsValue> for T or even impl From<JsValue> for T.
See Rust TryInto.
You can provide your custom conversion from the JsValue
with the try_from_js attribute.
The expected type for the parsing expression is FnOnce(JsValue) -> Result<T, _>.
Not that we do not care about the error type because
the error case is ignored.
The default expression is |value| value.try_into().
Example to convert a custom type that wraps a bool:
use dioxus::prelude::*;
use dioxus_web_component::web_component;
use std::convert::Infallible;
use wasm_bindgen::JsValue;
#[derive(Clone, PartialEq, Default)]
pub struct MyProp(bool);
#[web_component]
fn MyComponent(
#[property(
js_type = "bool",
try_from_js= |value| Ok::<_, Infallible>(MyProp(value.is_truthy())),
try_into_js = |prop| {
let js_value = if prop.0 {
JsValue::TRUE
} else {
JsValue::FALSE
};
Ok::<_, Infallible>(js_value)
},
)]
prop2: MyProp,
// ...
) -> Element {
todo!()
}But in that situation, the recommended way is to implement From<MyProp> for JsValue and
use the wasm-bindgen macro:
use dioxus::prelude::*;
use dioxus_web_component::web_component;
use wasm_bindgen::prelude::*;
// mapping to JsValue done by the #[wasm_bindgen] macro
#[wasm_bindgen]
#[derive(Clone, PartialEq, Default)]
pub struct MyProp(bool);
// mapping from JsValue
impl From<JsValue> for MyProp {
fn from(value: JsValue) -> Self {
Self(value.is_truthy())
}
}
#[web_component]
fn MyComponent(
// only need to declare the typescript type
#[property(js_type = "bool")] prop2: MyProp,
) -> Element {
todo!()
}- Typescript generation with
js_type,no_typescript
The macro try to generate generate the typescript definition of the web-component.
In some cases, it need more information, so you had to specify the js_type attribute,
or disable the typescript generation with no_typescript = true.
See the example above
§Events
The web component could send custom events.
If the type of the component parameter is EventHandler, the parameter is detected as an event.
Because this detection is not fully reliable, you could enforce a parameter to be
an event with the #[event] annotation.
The custom event detail corresponds to the generic type of the Dioxus EventHandler.
⚠️ IMPORTANT: The event type needs to implement Into<JsValue> and be 'static (does not have any reference).
You may need to implement it manually.
You could use serde-wasm-bindgen, gloo_utils::format::JsValueSerdeExt, wasm_bindgen::UnwrapThrowExt
to implement the Into<JsValue> trait.
name
The HTML event name is detected from the parameter name by removing the on_ (or on) prefix
and converting the name to kebab-case.
You can choose your value with the name attribute like #[event(name = "build")]
to dispatch a build event.
no_bubble
By default, the event bubbles up through the DOM.
You can avoid the bubbling with #[event(no_bubble = true)].
no_cancel
By default, the event is cancelable.
You can avoid the bubbling with #[event(no_cancel = true)].