Improve inline docs (#2460)

Improve inline docs

* improve incorrect event handler return error message

* Improve event handler docs

* document the eval functions

* document spawn and common spawn errors

* fix event handler docs

* add notes about how you use attributes and elements in rsx

* add doc aliases for attributes and events we rename

* add some more aliases for common search terms

* don't doc ignore any public examples in core

* don't ignore public doc examples in ssr

* don't ignore examples in the dioxus package readme

* add a warning when you launch without a renderer enabled

* fix some outdated element docs

* add a bunch of examples to resource

* add notes about desktop events

* add more docs for use_resource

* add on_unimplemented hint to Dependency

* fix some unresolved links

* add examples to each of the router traits

* add not implemented errors for router traits

* add an example to the routable trait

* expand rsx macro docs

* improve memo docs

* update the dioxus readme

* mention dioxus crate features in the docs

* fix a bunch of doc tests

* fix html doc tests

* fix router doc tests

* fix dioxus signals doc tests

* fix dioxus ssr doc tests

* fix use_future example in the hooks cheat sheet

* add a javascript alias for eval

* fix hook explanation values

* remove unused embed-doc-image dependency
This commit is contained in:
Evan Almloff authored and GitHub committed 2024-06-06 18:15:17 -07:00
1 parent 054351139f
commit 0127501dbf
71 files changed
+4132 -1162

No files matched your search

+4
View File
@@ -27,6 +27,7 @@ rfd = { version = "0.14", optional = true }
futures-channel = { workspace = true }
serde_json = { version = "1", optional = true }
tracing.workspace = true
rustversion = "1.0.17"
[dependencies.web-sys]
optional = true
@@ -50,6 +51,9 @@ features = [
[dev-dependencies]
serde_json = "1"
dioxus = { workspace = true }
dioxus-web = { workspace = true }
tokio = { workspace = true, features = ["time"] }
[features]
default = ["serialize", "mounted", "eval"]
@@ -0,0 +1,133 @@
## Compiler errors you may run into while using event handlers
<details>
<summary>function requires argument type to outlive `'static`</summary>
Event handler in Dioxus need only access data that can last for the entire lifetime of the application. That generally means data that is moved into the closure. **If you get this error, you may have forgotten to add `move` to your closure.**
Broken component:
```rust, compile_fail
# use dioxus::prelude::*;
// We return an Element which can last as long as the component is on the screen
fn App() -> Element {
// Signals are `Copy` which makes them very easy to move into `'static` closures like event handlers
let state = use_signal(|| "hello world".to_string());
rsx! {
button {
// ❌ Without `move`, rust will try to borrow the `state` signal which fails because the state signal is dropped at the end of the function
onclick: |_| {
println!("You clicked the button! The state is: {state}");
},
"Click me"
}
}
// The state signal is dropped here, but the event handler still needs to access it
}
```
Fixed component:
```rust, no_run
# use dioxus::prelude::*;
fn App() -> Element {
let state = use_signal(|| "hello world".to_string());
rsx! {
button {
// ✅ The `move` keyword tells rust it can move the `state` signal into the closure. Since the closure owns the signal state, it can read it even after the function returns
onclick: move |_| {
println!("You clicked the button! The state is: {state}");
},
"Click me"
}
}
}
```
</details>
<details>
<summary>use of moved value: `your_value` value used here after move</summary>
Data in rust has a single owner. If you run into this error, you have likely tried to move data that isn't `Copy` into two different closures. **You can fix this issue by making your data `Copy` or calling `clone` on it before you move it into the closure.**
Broken component:
```rust, compile_fail
# use dioxus::prelude::*;
// `MyComponent` accepts a string which cannot be copied implicitly
#[component]
fn MyComponent(string: String) -> Element {
rsx! {
button {
// ❌ We are moving the string into the onclick handler which means we can't access it elsewhere
onclick: move |_| {
println!("{string}");
},
"Print hello world"
}
button {
// ❌ Since we already moved the string, we can't move it into the onclick handler again. This will cause a compiler error
onclick: move |_| {
println!("{string}");
},
"Print hello world again"
}
}
}
```
You can fix this issue by either:
- Making your data `Copy` with `ReadOnlySignal`:
```rust, no_run
# use dioxus::prelude::*;
// `MyComponent` accepts `ReadOnlySignal<String>` which implements `Copy`
#[component]
fn MyComponent(string: ReadOnlySignal<String>) -> Element {
rsx! {
button {
// ✅ Because the `string` signal is `Copy`, we can copy it into the closure while still having access to it elsewhere
onclick: move |_| println!("{}", string),
"Print hello world"
}
button {
// ✅ Since `string` is `Copy`, we can move it into the onclick handler again
onclick: move |_| println!("{}", string),
"Print hello world again"
}
}
}
```
- Calling `clone` on your data before you move it into the closure:
```rust, no_run
# use dioxus::prelude::*;
// `MyComponent` accepts a string which doesn't implement `Copy`
#[component]
fn MyComponent(string: String) -> Element {
rsx! {
button {
// ✅ The string only has one owner. We could move it into this closure, but since we want to use the string in other closures later, we will clone it instead
onclick: {
// Clone the string in a new block
let string = string.clone();
// Then move the cloned string into the closure
move |_| println!("{}", string)
},
"Print hello world"
}
button {
// ✅ We don't use the string after this closure, so we can just move it into the closure directly
onclick: move |_| println!("{}", string),
"Print hello world again"
}
}
}
```
</details>
+125
View File
@@ -0,0 +1,125 @@
# Communicating with JavaScript
You can use the `eval` function to execute JavaScript code in your application with the desktop, mobile, web or liveview renderers. Eval takes a block of JavaScript code (that may be asynchronous) and returns a `UseEval` object that you can use to send data to the JavaScript code and receive data from it.
<div class="warning">
## Safety
Please be careful when executing JavaScript code with `eval`. You should only execute code that you trust. **This applies especially to web targets, where the JavaScript context has access to most, if not all of your application data.** Running untrusted code can lead to a [cross-site scripting](https://developer.mozilla.org/en-US/docs/Glossary/Cross-site_scripting) (XSS) vulnerability.
</div>
```rust
use dioxus::prelude::*;
fn App() -> Element {
rsx! {
button {
onclick: move |_| async move {
// Eval is a global function you can use anywhere inside Dioxus. It will execute the given JavaScript code.
let result = eval(r#"console.log("Hello World");
return "Hello World";"#);
// You can use the `await` keyword to wait for the result of the JavaScript code.
println!("{:?}", result.await);
},
"Log Hello World"
}
}
}
```
## Sending data to JavaScript
When you execute JavaScript code with `eval`, you can pass data to it by formatting the value into the JavaScript code or sending values to the `UseEval` channel.
```rust
use dioxus::prelude::*;
fn app() -> Element {
rsx! {
button {
onclick: move |_| {
// You can pass initial data to the eval function by formatting it into the JavaScript code.
const LOOP_COUNT: usize = 10;
let eval = eval(&format!(r#"for(let i = 0; i < {LOOP_COUNT}; i++) {{
// You can receive values asynchronously with the the `await dioxus.recv()` method.
let value = await dioxus.recv();
console.log("Received", value);
}}"#));
// You can send values from rust to the JavaScript code with the `send` method on the object returned by `eval`.
for i in 0..LOOP_COUNT {
eval.send(i.into()).unwrap();
}
},
"Log Count"
}
}
}
```
## Sending data from JavaScript
The `UseEval` struct also contains methods for receiving values you send from JavaScript. You can use the `dioxus.send()` method to send values to the JavaScript code and the `UseEval::recv()` method to receive values from the JavaScript code.
```rust
use dioxus::prelude::*;
fn app() -> Element {
rsx! {
button {
onclick: move |_| async move {
// You can send values from rust to the JavaScript code by using the `send` method on the object returned by `eval`.
let mut eval = eval(r#"for(let i = 0; i < 10; i++) {
// You can send values asynchronously with the `dioxus.send()` method.
dioxus.send(i);
}"#);
// You can receive values from the JavaScript code with the `recv` method on the object returned by `eval`.
for _ in 0..10 {
let value = eval.recv().await.unwrap();
println!("Received {}", value);
}
},
"Log Count"
}
}
}
```
## Interacting with the DOM with Eval
You can also use the `eval` function to execute JavaScript code that reads or modifies the DOM. If you want to interact with the mounted DOM, you need to use `eval` inside the [`dioxus_hooks::use_effect`] hook which runs after the component has been mounted.
```rust
use dioxus::prelude::*;
const SCRIPT: &str = r#"
let element = document.getElementById("my-element");
element.innerHTML = "Hello World";
return element.getAttribute("data-count");
"#;
fn app() -> Element {
// ❌ You shouldn't run eval in the body of a component. This will run before the component has been mounted
// eval(SCRIPT);
// ✅ You should run eval inside an effect or event. This will run after the component has been mounted
use_effect(move || {
spawn(async {
let count = eval(SCRIPT).await;
println!("Count is {:?}", count);
});
});
rsx! {
div {
id: "my-element",
"data-count": "123",
}
}
}
```
+86
View File
@@ -0,0 +1,86 @@
# Event Handlers
Event Handlers let you react to user input in your application. In Dioxus, event handlers accept a closure that is called when the event occurs:
```rust, no_run
use dioxus::prelude::*;
fn App() -> Element {
rsx! {
button {
// The `onclick` event accepts a closure with the signature `fn(Event)`
onclick: |event_data| println!("clicked! I got the event data: {event_data:?}"),
"Click me"
}
}
}
```
## Event Lifetimes
Events take a closure with the `'static` lifetime. This means that the closure can only access data that either exists for the entire lifetime of the application, or data that you move into the closure.
State in dioxus is `copy` which makes it very easy to move into `'static` closures like event handlers:
```rust, no_run
# use dioxus::prelude::*;
let mut count = use_signal(|| 0);
rsx! {
button {
// Since we added the `move` keyword, the closure will move the `count` signal into the closure
onclick: move |_| {
// This will panic because the `count` signal is not in scope
count.set(count() + 1);
},
"Click me"
}
};
```
If you need to access data that is not `Copy`, you may need to clone the data before you move it into the closure:
```rust, no_run
# use dioxus::prelude::*;
// String is not `Copy`
let string = "hello world".to_string();
rsx! {
button {
// The string only has one owner. We could move it into this closure, but since we want to use the string in other closures later, we will clone it instead
onclick: {
// Clone the string in a new block
let string = string.clone();
// Then move the cloned string into the closure
move |_| println!("{}", string)
},
"Print hello world"
}
button {
// We don't use the string after this closure, so we can just move it into the closure directly
onclick: move |_| println!("{}", string),
"Print hello world again"
}
};
```
## Async Event Handlers
In addition to closures that return nothing, you can also use async closures to handle events. If you return an async block from an event handler, dioxus will automatically spawn it:
```rust, no_run
use dioxus::prelude::*;
fn App() -> Element {
rsx! {
button {
// The `onclick` event can also accept a closure that returns an async block
onclick: move |_| async move {
tokio::time::sleep(std::time::Duration::from_secs(1)).await;
println!("You clicked the button one second ago!");
},
"Click me"
}
}
}
```
File diff suppressed because it is too large. Load diff
+236 -46
View File
@@ -13,37 +13,147 @@ pub type AttributeDiscription = (&'static str, Option<&'static str>, bool);
macro_rules! impl_attribute {
(
$(#[$attr_method:meta])*
$fil:ident: $vil:ident (DEFAULT),
$element:ident {
$(#[$attr_method:meta])*
$fil:ident: $vil:ident (DEFAULT),
}
) => {
$(#[$attr_method])*
///
/// ## Usage in rsx
///
/// ```rust, no_run
/// # use dioxus::prelude::*;
#[doc = concat!("let ", stringify!($fil), " = \"value\";")]
///
/// rsx! {
/// // Attributes need to be under the element they modify
#[doc = concat!(" ", stringify!($element), " {")]
/// // Attributes are followed by a colon and then the value of the attribute
#[doc = concat!(" ", stringify!($fil), ": \"value\"")]
/// }
#[doc = concat!(" ", stringify!($element), " {")]
/// // Or you can use the shorthand syntax if you have a variable in scope that has the same name as the attribute
#[doc = concat!(" ", stringify!($fil), ",")]
/// }
/// };
/// ```
pub const $fil: AttributeDiscription = (stringify!($fil), None, false);
};
(
$(#[$attr_method:meta])*
$fil:ident: $vil:ident ($name:literal),
$element:ident {
$(#[$attr_method:meta])*
$fil:ident: $vil:ident ($name:literal),
}
) => {
$(#[$attr_method])*
///
/// ## Usage in rsx
///
/// ```rust, no_run
/// # use dioxus::prelude::*;
#[doc = concat!("let ", stringify!($fil), " = \"value\";")]
///
/// rsx! {
/// // Attributes need to be under the element they modify
#[doc = concat!(" ", stringify!($element), " {")]
/// // Attributes are followed by a colon and then the value of the attribute
#[doc = concat!(" ", stringify!($fil), ": \"value\"")]
/// }
#[doc = concat!(" ", stringify!($element), " {")]
/// // Or you can use the shorthand syntax if you have a variable in scope that has the same name as the attribute
#[doc = concat!(" ", stringify!($fil), ",")]
/// }
/// };
/// ```
pub const $fil: AttributeDiscription = ($name, None, false);
};
(
$(#[$attr_method:meta])*
$fil:ident: $vil:ident (volatile),
$element:ident {
$(#[$attr_method:meta])*
$fil:ident: $vil:ident (volatile),
}
) => {
$(#[$attr_method])*
///
/// ## Usage in rsx
///
/// ```rust, no_run
/// # use dioxus::prelude::*;
#[doc = concat!("let ", stringify!($fil), " = \"value\";")]
///
/// rsx! {
/// // Attributes need to be under the element they modify
#[doc = concat!(" ", stringify!($element), " {")]
/// // Attributes are followed by a colon and then the value of the attribute
#[doc = concat!(" ", stringify!($fil), ": \"value\"")]
/// }
#[doc = concat!(" ", stringify!($element), " {")]
/// // Or you can use the shorthand syntax if you have a variable in scope that has the same name as the attribute
#[doc = concat!(" ", stringify!($fil), ",")]
/// }
/// };
/// ```
pub const $fil: AttributeDiscription = (stringify!($fil), None, true);
};
(
$(#[$attr_method:meta])*
$fil:ident: $vil:ident (in $ns:literal),
$element:ident {
$(#[$attr_method:meta])*
$fil:ident: $vil:ident (in $ns:literal),
}
) => {
$(#[$attr_method])*
///
/// ## Usage in rsx
///
/// ```rust, no_run
/// # use dioxus::prelude::*;
#[doc = concat!("let ", stringify!($fil), " = \"value\";")]
///
/// rsx! {
/// // Attributes need to be under the element they modify
#[doc = concat!(" ", stringify!($element), " {")]
/// // Attributes are followed by a colon and then the value of the attribute
#[doc = concat!(" ", stringify!($fil), ": \"value\"")]
/// }
#[doc = concat!(" ", stringify!($element), " {")]
/// // Or you can use the shorthand syntax if you have a variable in scope that has the same name as the attribute
#[doc = concat!(" ", stringify!($fil), ",")]
/// }
/// };
/// ```
pub const $fil: AttributeDiscription = (stringify!($fil), Some($ns), false)
};
(
$(#[$attr_method:meta])*
$fil:ident: $vil:ident (in $ns:literal : volatile),
$element:ident {
$(#[$attr_method:meta])*
$fil:ident: $vil:ident (in $ns:literal : volatile),
}
) => {
$(#[$attr_method])*
///
/// ## Usage in rsx
///
/// ```rust, no_run
/// # use dioxus::prelude::*;
#[doc = concat!("let ", stringify!($fil), " = \"value\";")]
///
/// rsx! {
/// // Attributes need to be under the element they modify
#[doc = concat!(" ", stringify!($element), " {")]
/// // Attributes are followed by a colon and then the value of the attribute
#[doc = concat!(" ", stringify!($fil), ": \"value\"")]
/// }
#[doc = concat!(" ", stringify!($element), " {")]
/// // Or you can use the shorthand syntax if you have a variable in scope that has the same name as the attribute
#[doc = concat!(" ", stringify!($fil), ",")]
/// }
/// };
/// ```
pub const $fil: AttributeDiscription = (stringify!($fil), Some($ns), true)
};
}
@@ -114,6 +224,30 @@ macro_rules! impl_element {
) => {
#[allow(non_camel_case_types)]
$(#[$attr])*
///
/// ## Usage in rsx
///
/// ```rust, no_run
/// # use dioxus::prelude::*;
/// # let attributes = vec![];
/// # fn ChildComponent() -> Element { todo!() }
/// # let raw_expression: Element = rsx! {};
/// rsx! {
/// // Elements are followed by braces that surround any attributes and children for that element
#[doc = concat!(" ", stringify!($name), " {")]
/// // Add any attributes first
/// class: "my-class",
/// "custom-attribute-name": "value",
/// // Then add any attributes you are spreading into this element
/// ..attributes,
/// // Then add any children elements, components, text nodes, or raw expressions
/// div {}
/// ChildComponent {}
/// "child text"
/// {raw_expression}
/// }
/// };
/// ```
pub mod $name {
#[allow(unused)]
use super::*;
@@ -124,8 +258,10 @@ macro_rules! impl_element {
$(
impl_attribute!(
$(#[$attr_method])*
$fil: $vil ($extra),
$name {
$(#[$attr_method])*
$fil: $vil ($extra),
}
);
)*
}
@@ -141,6 +277,30 @@ macro_rules! impl_element {
}
) => {
$(#[$attr])*
///
/// ## Usage in rsx
///
/// ```rust, no_run
/// # use dioxus::prelude::*;
/// # let attributes = vec![];
/// # fn ChildComponent() -> Element { todo!() }
/// # let raw_expression: Element = rsx! {};
/// rsx! {
/// // Elements are followed by braces that surround any attributes and children for that element
#[doc = concat!(" ", stringify!($name), " {")]
/// // Add any attributes first
/// color: "red",
/// "custom-attribute-name": "value",
/// // Then add any attributes you are spreading into this element
/// ..attributes,
/// // Then add any children elements, components, text nodes, or raw expressions
/// circle { cx: "10", cy: "10", r: "2", fill: "red" }
/// ChildComponent {}
/// "child text"
/// {raw_expression}
/// }
/// };
/// ```
pub mod $name {
#[allow(unused)]
use super::*;
@@ -151,8 +311,10 @@ macro_rules! impl_element {
$(
impl_attribute!(
$(#[$attr_method])*
$fil: $vil ($extra),
$name {
$(#[$attr_method])*
$fil: $vil ($extra),
}
);
)*
}
@@ -169,6 +331,30 @@ macro_rules! impl_element {
) => {
#[allow(non_camel_case_types)]
$(#[$attr])*
///
/// ## Usage in rsx
///
/// ```rust, no_run
/// # use dioxus::prelude::*;
/// # let attributes = vec![];
/// # fn ChildComponent() -> Element { todo!() }
/// # let raw_expression: Element = rsx! {};
/// rsx! {
/// // Elements are followed by braces that surround any attributes and children for that element
#[doc = concat!(" ", stringify!($element), " {")]
/// // Add any attributes first
/// color: "red",
/// "custom-attribute-name": "value",
/// // Then add any attributes you are spreading into this element
/// ..attributes,
/// // Then add any children elements, components, text nodes, or raw expressions
/// circle { cx: "10", cy: "10", r: "2", fill: "red" }
/// ChildComponent {}
/// "child text"
/// {raw_expression}
/// }
/// };
/// ```
pub mod $element {
#[allow(unused)]
use super::*;
@@ -179,8 +365,10 @@ macro_rules! impl_element {
$(
impl_attribute!(
$(#[$attr_method])*
$fil: $vil ($extra),
$element {
$(#[$attr_method])*
$fil: $vil ($extra),
}
);
)*
}
@@ -390,6 +578,30 @@ macro_rules! builder_constructors {
pub enum CompleteWithBraces {
$(
$(#[$attr])*
///
/// ## Usage in rsx
///
/// ```rust, no_run
/// # use dioxus::prelude::*;
/// # let attributes = vec![];
/// # fn ChildComponent() -> Element { todo!() }
/// # let raw_expression: Element = rsx! {};
/// rsx! {
/// // Elements are followed by braces that surround any attributes and children for that element
#[doc = concat!(" ", stringify!($name), " {")]
/// // Add any attributes first
/// class: "my-class",
/// "custom-attribute-name": "value",
/// // Then add any attributes you are spreading into this element
/// ..attributes,
/// // Then add any children elements, components, text nodes, or raw expressions
/// div {}
/// ChildComponent {}
/// "child text"
/// {raw_expression}
/// }
/// };
/// ```
$name {}
),*
}
@@ -521,14 +733,6 @@ builder_constructors! {
/// - The most important heading is `<h1>` and the least important heading is `<h6>`.
/// - The `<h1>` heading is the first heading in the document.
/// - The `<h1>` heading is usually a large bolded font.
///
/// # Usage
///
/// ```rust, ignore
/// html!(<h1> A header element </h1>)
/// rsx!(h1 { "A header element" })
/// LazyNodes::new(|f| f.el(h1).children([f.text("A header element")]).finish())
/// ```
h1 None {};
/// Build a
@@ -541,13 +745,6 @@ builder_constructors! {
/// - The most important heading is `<h1>` and the least important heading is `<h6>`.
/// - The `<h2>` heading is the second heading in the document.
/// - The `<h2>` heading is usually a large bolded font.
///
/// # Usage
/// ```rust, ignore
/// html!(<h2> A header element </h2>)
/// rsx!(h2 { "A header element" })
/// LazyNodes::new(|f| f.el(h2).children([f.text("A header element")]).finish())
/// ```
h2 None {};
/// Build a
@@ -556,10 +753,10 @@ builder_constructors! {
///
/// # About
/// - The HTML <h1> element is found within the <body> tag.
/// - Headings can range from <h1> to <h6>.
/// - The most important heading is <h1> and the least important heading is <h6>.
/// - The <h1> heading is the first heading in the document.
/// - The <h1> heading is usually a large bolded font.
/// - Headings can range from `<h1>` to `<h6>`.
/// - The most important heading is `<h1>`` and the least important heading is `<h6>`.
/// - The `<h1>` heading is the first heading in the document.
/// - The `<h1>` heading is usually a large bolded font.
h3 None {};
/// Build a
@@ -612,20 +809,13 @@ builder_constructors! {
/// Part of the HTML namespace. Only works in HTML-compatible renderers
///
/// ## Definition and Usage
/// - The <div> tag defines a division or a section in an HTML document.
/// - The <div> tag is used as a container for HTML elements - which is then styled with CSS or manipulated with JavaScript.
/// - The <div> tag is easily styled by using the class or id attribute.
/// - Any sort of content can be put inside the <div> tag!
/// - The `<div>` tag defines a division or a section in an HTML document.
/// - The `<div>` tag is used as a container for HTML elements - which is then styled with CSS or manipulated with JavaScript.
/// - The `<div>` tag is easily styled by using the class or id attribute.
/// - Any sort of content can be put inside the `<div>` tag!
///
/// Note: By default, browsers always place a line break before and after the <div> element.
///
/// ## Usage
/// ```rust, ignore
/// html!(<div> A header element </div>)
/// rsx!(div { "A header element" })
/// LazyNodes::new(|f| f.element(div, &[], &[], &[], None))
/// ```
///
/// ## References:
/// - <https://developer.mozilla.org/en-US/docs/Web/HTML/Element/div>
/// - <https://www.w3schools.com/tags/tag_div.asp>
+7 -2
View File
@@ -1,4 +1,5 @@
#![allow(clippy::await_holding_refcell_ref)]
#![doc = include_str!("../docs/eval.md")]
use dioxus_core::prelude::*;
use generational_box::{AnyStorage, GenerationalBox, UnsyncStorage};
@@ -8,7 +9,7 @@ use std::rc::Rc;
use std::task::{Context, Poll};
/// A struct that implements EvalProvider is sent through [`ScopeState`]'s provide_context function
/// so that [`use_eval`] can provide a platform agnostic interface for evaluating JavaScript code.
/// so that [`eval`] can provide a platform agnostic interface for evaluating JavaScript code.
pub trait EvalProvider {
fn new_evaluator(&self, js: String) -> GenerationalBox<Box<dyn Evaluator>>;
}
@@ -47,6 +48,8 @@ pub fn eval_provider() -> EvalCreator {
as Rc<dyn Fn(&str) -> UseEval>
}
#[doc = include_str!("../docs/eval.md")]
#[doc(alias = "javascript")]
pub fn eval(script: &str) -> UseEval {
let eval_provider = dioxus_core::prelude::try_consume_context::<Rc<dyn EvalProvider>>()
// Create a dummy provider that always hiccups when trying to evaluate
@@ -84,7 +87,9 @@ pub fn eval(script: &str) -> UseEval {
UseEval::new(eval_provider.new_evaluator(script.to_string()))
}
/// A wrapper around the target platform's evaluator.
/// A wrapper around the target platform's evaluator that lets you send and receive data from JavaScript spawned by [`eval`].
///
#[doc = include_str!("../docs/eval.md")]
#[derive(Clone, Copy)]
pub struct UseEval {
evaluator: GenerationalBox<Box<dyn Evaluator>>,
+45 -2
View File
@@ -30,7 +30,7 @@ impl FormValue {
self.0.first().unwrap().clone()
}
/// Convert into Vec<String>
/// Convert into [`Vec<String>`]
pub fn to_vec(self) -> Vec<String> {
self.0.clone()
}
@@ -287,7 +287,50 @@ impl_event! {
/// onchange
onchange
/// oninput handler
/// The `oninput` event is fired when the value of a `<input>`, `<select>`, or `<textarea>` element is changed.
///
/// There are two main approaches to updating your input element:
/// 1) Controlled inputs directly update the value of the input element as the user interacts with the element
///
/// ```rust
/// use dioxus::prelude::*;
///
/// fn App() -> Element {
/// let mut value = use_signal(|| "hello world".to_string());
///
/// rsx! {
/// input {
/// // We directly set the value of the input element to our value signal
/// value: "{value}",
/// // The `oninput` event handler will run every time the user changes the value of the input element
/// // We can set the `value` signal to the new value of the input element
/// oninput: move |event| value.set(event.value())
/// }
/// // Since this is a controlled input, we can also update the value of the input element directly
/// button {
/// onclick: move |_| value.write().clear(),
/// "Clear"
/// }
/// }
/// }
/// ```
///
/// 2) Uncontrolled inputs just read the value of the input element as it changes
///
/// ```rust
/// use dioxus::prelude::*;
///
/// fn App() -> Element {
/// rsx! {
/// input {
/// // In uncontrolled inputs, we don't set the value of the input element directly
/// // But you can still read the value of the input element
/// oninput: move |event| println!("{}", event.value()),
/// }
/// // Since we don't directly control the value of the input element, we can't easily modify it
/// }
/// }
/// ```
oninput
/// oninvalid
@@ -1,3 +1,5 @@
#![doc = include_str!("../../docs/event_handlers.md")]
use std::any::Any;
use std::sync::RwLock;
@@ -11,6 +13,17 @@ macro_rules! impl_event {
) => {
$(
$( #[$attr] )*
/// <details open>
/// <summary>General Event Handler Information</summary>
///
#[doc = include_str!("../../docs/event_handlers.md")]
///
/// </details>
///
#[doc = include_str!("../../docs/common_event_handler_errors.md")]
$(
#[doc(alias = $js_name)]
)?
#[inline]
pub fn $name<E: crate::EventReturn<T>, T>(mut _f: impl FnMut(::dioxus_core::Event<$data>) -> E + 'static) -> ::dioxus_core::Attribute {
::dioxus_core::Attribute::new(
@@ -356,6 +369,15 @@ pub fn event_bubbles(evt: &str) -> bool {
}
#[doc(hidden)]
#[rustversion::attr(
since(1.78.0),
diagnostic::on_unimplemented(
message = "`EventHandlerReturn` is not implemented for `{Self}`",
label = "Return Value",
note = "Event handlers in dioxus need to return either: nothing (the unit type `()`), or an async block that dioxus will automatically spawn",
note = "You likely need to add a semicolon to the end of the event handler to make it return nothing",
)
)]
pub trait EventReturn<P>: Sized {
fn spawn(self) {}
}
+67 -1
View File
@@ -53,6 +53,7 @@ impl RenderedElementBacking for () {
/// The way that scrolling should be performed
#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
#[doc(alias = "ScrollIntoViewOptions")]
pub enum ScrollBehavior {
/// Scroll to the element immediately
#[cfg_attr(feature = "serialize", serde(rename = "instant"))]
@@ -84,21 +85,27 @@ impl MountedData {
}
/// Get the number of pixels that an element's content is scrolled
#[doc(alias = "scrollTop")]
#[doc(alias = "scrollLeft")]
pub async fn get_scroll_offset(&self) -> MountedResult<PixelsVector2D> {
self.inner.get_scroll_offset().await
}
/// Get the size of an element's content, including content not visible on the screen due to overflow
#[doc(alias = "scrollWidth")]
#[doc(alias = "scrollHeight")]
pub async fn get_scroll_size(&self) -> MountedResult<PixelsSize> {
self.inner.get_scroll_size().await
}
/// Get the bounding rectangle of the element relative to the viewport (this does not include the scroll position)
#[doc(alias = "getBoundingClientRect")]
pub async fn get_client_rect(&self) -> MountedResult<PixelsRect> {
self.inner.get_client_rect().await
}
/// Scroll to make the element visible
#[doc(alias = "scrollIntoView")]
pub fn scroll_to(
&self,
behavior: ScrollBehavior,
@@ -107,6 +114,8 @@ impl MountedData {
}
/// Set the focus on the element
#[doc(alias = "focus")]
#[doc(alias = "blur")]
pub fn set_focus(&self, focus: bool) -> Pin<Box<dyn Future<Output = MountedResult<()>>>> {
self.inner.set_focus(focus)
}
@@ -126,7 +135,64 @@ pub type MountedEvent = Event<MountedData>;
impl_event! [
MountedData;
/// mounted
#[doc(alias = "ref")]
#[doc(alias = "createRef")]
#[doc(alias = "useRef")]
/// The onmounted event is fired when the element is first added to the DOM. This event gives you a [`MountedData`] object and lets you interact with the raw DOM element.
///
/// This event is fired once per element. If you need to access the element multiple times, you can store the [`MountedData`] object in a [`use_signal`] hook and use it as needed.
///
/// # Examples
///
/// ```rust, no_run
/// # use dioxus::prelude::*;
/// fn App() -> Element {
/// let mut header_element = use_signal(|| None);
///
/// rsx! {
/// div {
/// h1 {
/// // The onmounted event will run the first time the h1 element is mounted
/// onmounted: move |element| header_element.set(Some(element.data())),
/// "Scroll to top example"
/// }
///
/// for i in 0..100 {
/// div { "Item {i}" }
/// }
///
/// button {
/// // When you click the button, if the header element has been mounted, we scroll to that element
/// onclick: move |_| async move {
/// if let Some(header) = header_element.cloned() {
/// let _ = header.scroll_to(ScrollBehavior::Smooth).await;
/// }
/// },
/// "Scroll to top"
/// }
/// }
/// }
/// }
/// ```
///
/// The `MountedData` struct contains cross platform APIs that work on the desktop, mobile, liveview and web platforms. For the web platform, you can also downcast the `MountedData` event to the `web-sys::Element` type for more web specific APIs:
///
/// ```rust, no_run
/// # use dioxus::prelude::*;
/// # use dioxus_web::WebEventExt;
/// fn App() -> Element {
/// rsx! {
/// div {
/// id: "some-id",
/// onmounted: move |element| {
/// // You can use the web_event trait to downcast the element to a web specific event. For the mounted event, this will be a web_sys::Element
/// let web_sys_element = element.web_event();
/// assert_eq!(web_sys_element.id(), "some-id");
/// }
/// }
/// }
/// }
/// ```
onmounted
];