mirror of
https://github.com/DioxusLabs/dioxus
synced 2024-11-10 06:34:20 +00:00
polish: examples
This commit is contained in:
parent
4b6ca05f2c
commit
1a2f91ed91
19 changed files with 327 additions and 183 deletions
|
@ -51,8 +51,10 @@ argh = "0.1.5"
|
|||
env_logger = "*"
|
||||
async-std = { version = "1.9.0", features = ["attributes"] }
|
||||
rand = { version = "0.8.4", features = ["small_rng"] }
|
||||
surf = { version = "2.2.0", git = "https://github.com/jkelleyrtp/surf/", branch = "jk/fix-the-wasm" }
|
||||
gloo-timers = "0.2.1"
|
||||
# surf = { version = "2.3.1", default-features = false, features = [
|
||||
# "wasm-client",
|
||||
# ] }
|
||||
|
||||
[target.'cfg(target_arch = "wasm32")'.dev-dependencies]
|
||||
gloo-timers = "0.2.1"
|
||||
|
|
|
@ -1,41 +1,43 @@
|
|||
//! Example: README.md showcase
|
||||
//!
|
||||
//! The example from the README.md
|
||||
/*
|
||||
This example shows how to use async and loops to implement a coroutine in a component. Coroutines can be controlled via
|
||||
the `TaskHandle` object.
|
||||
*/
|
||||
|
||||
use dioxus::prelude::*;
|
||||
use gloo_timers::future::TimeoutFuture;
|
||||
|
||||
fn main() {
|
||||
dioxus::desktop::launch(App, |c| c).expect("faield to launch");
|
||||
dioxus::desktop::launch(App, |c| c).unwrap();
|
||||
}
|
||||
|
||||
pub static App: FC<()> = |cx, props| {
|
||||
pub static App: FC<()> = |cx, _| {
|
||||
let count = use_state(cx, || 0);
|
||||
let mut direction = use_state(cx, || 1);
|
||||
|
||||
let (async_count, dir) = (count.for_async(), *direction);
|
||||
let (task, _result) = use_task(cx, move || async move {
|
||||
|
||||
let (task, _) = use_task(cx, move || async move {
|
||||
loop {
|
||||
gloo_timers::future::TimeoutFuture::new(250).await;
|
||||
TimeoutFuture::new(250).await;
|
||||
*async_count.get_mut() += dir;
|
||||
}
|
||||
});
|
||||
|
||||
cx.render(rsx! {
|
||||
div {
|
||||
h1 {"count is {count}"}
|
||||
button {
|
||||
"Stop counting"
|
||||
onclick: move |_| task.stop()
|
||||
}
|
||||
button {
|
||||
"Start counting"
|
||||
onclick: move |_| task.resume()
|
||||
}
|
||||
button {
|
||||
"Switch counting direcion"
|
||||
onclick: move |_| {
|
||||
direction *= -1;
|
||||
task.restart();
|
||||
}
|
||||
rsx!(cx, div {
|
||||
h1 {"count is {count}"}
|
||||
button {
|
||||
"Stop counting"
|
||||
onclick: move |_| task.stop()
|
||||
}
|
||||
button {
|
||||
"Start counting"
|
||||
onclick: move |_| task.resume()
|
||||
}
|
||||
button {
|
||||
"Switch counting direcion"
|
||||
onclick: move |_| {
|
||||
direction *= -1;
|
||||
task.restart();
|
||||
}
|
||||
}
|
||||
})
|
||||
|
|
|
@ -1,20 +1,18 @@
|
|||
#![allow(non_snake_case)]
|
||||
//! Example: Extremely nested borrowing
|
||||
//! -----------------------------------
|
||||
//!
|
||||
//! Dioxus manages borrow lifetimes for you. This means any child may borrow from its parent. However, it is not possible
|
||||
//! to hand out an &mut T to children - all props are consumed by &P, so you'd only get an &&mut T.
|
||||
//!
|
||||
//! How does it work?
|
||||
//!
|
||||
//! Dioxus will manually drop closures and props - things that borrow data before the component is ran again. This is done
|
||||
//! "bottom up" from the lowest child all the way to the initiating parent. As it traverses each listener and prop, the
|
||||
//! drop implementation is manually called, freeing any memory and ensuring that memory is not leaked.
|
||||
//!
|
||||
//! We cannot drop from the parent to the children - if the drop implementation modifies the data, downstream references
|
||||
//! might be broken since we take an &mut T and and &T to the data. Instead, we work bottom up, making sure to remove any
|
||||
//! potential references to the data before finally giving out an &mut T. This prevents us from mutably aliasing the data,
|
||||
//! and is proven to be safe with MIRI.
|
||||
/*
|
||||
Dioxus manages borrow lifetimes for you. This means any child may borrow from its parent. However, it is not possible
|
||||
to hand out an &mut T to children - all props are consumed by &P, so you'd only get an &&mut T.
|
||||
|
||||
How does it work?
|
||||
|
||||
Dioxus will manually drop closures and props - things that borrow data before the component is ran again. This is done
|
||||
"bottom up" from the lowest child all the way to the initiating parent. As it traverses each listener and prop, the
|
||||
drop implementation is manually called, freeing any memory and ensuring that memory is not leaked.
|
||||
|
||||
We cannot drop from the parent to the children - if the drop implementation modifies the data, downstream references
|
||||
might be broken since we take an &mut T and and &T to the data. Instead, we work bottom up, making sure to remove any
|
||||
potential references to the data before finally giving out an &mut T. This prevents us from mutably aliasing the data,
|
||||
and is proven to be safe with MIRI.
|
||||
*/
|
||||
|
||||
use dioxus::prelude::*;
|
||||
|
||||
|
|
|
@ -1,25 +1,19 @@
|
|||
//! Example: Calculator
|
||||
//! -------------------------
|
||||
|
||||
fn main() {
|
||||
env_logger::init();
|
||||
dioxus::desktop::launch(App, |cfg| cfg).unwrap();
|
||||
}
|
||||
/*
|
||||
This example is a simple iOS-style calculator. This particular example can run any platform - Web, Mobile, Desktop.
|
||||
This calculator version uses React-style state management. All state is held as individual use_states.
|
||||
*/
|
||||
|
||||
use dioxus::events::on::*;
|
||||
use dioxus::prelude::*;
|
||||
|
||||
enum Operator {
|
||||
Add,
|
||||
Sub,
|
||||
Mul,
|
||||
Div,
|
||||
fn main() {
|
||||
dioxus::desktop::launch(APP, |cfg| cfg).unwrap();
|
||||
}
|
||||
|
||||
const App: FC<()> = |cx, props| {
|
||||
const APP: FC<()> = |cx, _| {
|
||||
let cur_val = use_state(cx, || 0.0_f64);
|
||||
let operator = use_state(cx, || None as Option<Operator>);
|
||||
let display_value = use_state(cx, || "".to_string());
|
||||
let operator = use_state(cx, || None);
|
||||
let display_value = use_state(cx, || String::from(""));
|
||||
|
||||
let clear_display = display_value == "0";
|
||||
let clear_text = if clear_display { "C" } else { "AC" };
|
||||
|
@ -62,11 +56,10 @@ const App: FC<()> = |cx, props| {
|
|||
};
|
||||
|
||||
let keydownhandler = move |evt: KeyboardEvent| match evt.key_code() {
|
||||
KeyCode::Backspace => {
|
||||
if !display_value.as_str().eq("0") {
|
||||
display_value.modify().pop();
|
||||
}
|
||||
}
|
||||
KeyCode::Add => operator.set(Some(Operator::Add)),
|
||||
KeyCode::Subtract => operator.set(Some(Operator::Sub)),
|
||||
KeyCode::Divide => operator.set(Some(Operator::Div)),
|
||||
KeyCode::Multiply => operator.set(Some(Operator::Mul)),
|
||||
KeyCode::Num0 => input_digit(0),
|
||||
KeyCode::Num1 => input_digit(1),
|
||||
KeyCode::Num2 => input_digit(2),
|
||||
|
@ -77,73 +70,62 @@ const App: FC<()> = |cx, props| {
|
|||
KeyCode::Num7 => input_digit(7),
|
||||
KeyCode::Num8 => input_digit(8),
|
||||
KeyCode::Num9 => input_digit(9),
|
||||
KeyCode::Add => operator.set(Some(Operator::Add)),
|
||||
KeyCode::Subtract => operator.set(Some(Operator::Sub)),
|
||||
KeyCode::Divide => operator.set(Some(Operator::Div)),
|
||||
KeyCode::Multiply => operator.set(Some(Operator::Mul)),
|
||||
KeyCode::Backspace => {
|
||||
if !display_value.as_str().eq("0") {
|
||||
display_value.modify().pop();
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
};
|
||||
|
||||
cx.render(rsx! {
|
||||
div { class: "calculator", onkeydown: {keydownhandler}
|
||||
div { class: "input-keys"
|
||||
div { class: "function-keys"
|
||||
CalculatorKey { name: "key-clear", onclick: {clear_key} "{clear_text}" }
|
||||
CalculatorKey { name: "key-sign", onclick: {toggle_sign}, "±"}
|
||||
CalculatorKey { name: "key-percent", onclick: {toggle_percent} "%"}
|
||||
}
|
||||
div { class: "digit-keys"
|
||||
CalculatorKey { name: "key-0", onclick: move |_| input_digit(0), "0" }
|
||||
CalculatorKey { name: "key-dot", onclick: move |_| input_dot(), "●" }
|
||||
use separator::Separatable;
|
||||
let formatted_display = cur_val.separated_string();
|
||||
|
||||
{(1..9).map(|k| rsx!{
|
||||
CalculatorKey { key: "{k}", name: "key-{k}", onclick: move |_| input_digit(k), "{k}" }
|
||||
})}
|
||||
}
|
||||
div { class: "operator-keys"
|
||||
CalculatorKey { name: "key-divide", onclick: move |_| operator.set(Some(Operator::Div)) "÷" }
|
||||
CalculatorKey { name: "key-multiply", onclick: move |_| operator.set(Some(Operator::Mul)) "×" }
|
||||
CalculatorKey { name: "key-subtract", onclick: move |_| operator.set(Some(Operator::Sub)) "−" }
|
||||
CalculatorKey { name: "key-add", onclick: move |_| operator.set(Some(Operator::Add)) "+" }
|
||||
CalculatorKey { name: "key-equals", onclick: move |_| perform_operation() "=" }
|
||||
}
|
||||
rsx!(cx, div {
|
||||
class: "calculator", onkeydown: {keydownhandler}
|
||||
div { class: "calculator-display", "{formatted_display}" }
|
||||
div { class: "input-keys"
|
||||
div { class: "function-keys"
|
||||
CalculatorKey { name: "key-clear", onclick: {clear_key} "{clear_text}" }
|
||||
CalculatorKey { name: "key-sign", onclick: {toggle_sign}, "±"}
|
||||
CalculatorKey { name: "key-percent", onclick: {toggle_percent} "%"}
|
||||
}
|
||||
div { class: "digit-keys"
|
||||
CalculatorKey { name: "key-0", onclick: move |_| input_digit(0), "0" }
|
||||
CalculatorKey { name: "key-dot", onclick: move |_| input_dot(), "●" }
|
||||
|
||||
{(1..9).map(|k| rsx!{
|
||||
CalculatorKey { key: "{k}", name: "key-{k}", onclick: move |_| input_digit(k), "{k}" }
|
||||
})}
|
||||
}
|
||||
div { class: "operator-keys"
|
||||
CalculatorKey { name: "key-divide", onclick: move |_| operator.set(Some(Operator::Div)) "÷" }
|
||||
CalculatorKey { name: "key-multiply", onclick: move |_| operator.set(Some(Operator::Mul)) "×" }
|
||||
CalculatorKey { name: "key-subtract", onclick: move |_| operator.set(Some(Operator::Sub)) "−" }
|
||||
CalculatorKey { name: "key-add", onclick: move |_| operator.set(Some(Operator::Add)) "+" }
|
||||
CalculatorKey { name: "key-equals", onclick: move |_| perform_operation() "=" }
|
||||
}
|
||||
}
|
||||
})
|
||||
};
|
||||
|
||||
enum Operator {
|
||||
Add,
|
||||
Sub,
|
||||
Mul,
|
||||
Div,
|
||||
}
|
||||
|
||||
#[derive(Props)]
|
||||
struct CalculatorKeyProps<'a> {
|
||||
/// Name!
|
||||
name: &'static str,
|
||||
|
||||
/// Click!
|
||||
onclick: &'a dyn Fn(MouseEvent),
|
||||
}
|
||||
|
||||
fn CalculatorKey<'a>(cx: Context<'a>, props: &'a CalculatorKeyProps) -> DomTree<'a> {
|
||||
cx.render(rsx! {
|
||||
button {
|
||||
class: "calculator-key {props.name}"
|
||||
onclick: {props.onclick}
|
||||
{cx.children()}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
#[derive(Props, PartialEq)]
|
||||
struct CalculatorDisplayProps {
|
||||
val: f64,
|
||||
}
|
||||
|
||||
fn CalculatorDisplay<'a>(cx: Context<'a>, props: &CalculatorDisplayProps) -> DomTree<'a> {
|
||||
use separator::Separatable;
|
||||
// Todo, add float support to the num-format crate
|
||||
let formatted = props.val.separated_string();
|
||||
// TODO: make it autoscaling with css
|
||||
cx.render(rsx! {
|
||||
div { class: "calculator-display"
|
||||
"{formatted}"
|
||||
}
|
||||
rsx!(cx, button {
|
||||
class: "calculator-key {props.name}"
|
||||
onclick: {props.onclick}
|
||||
{cx.children()}
|
||||
})
|
||||
}
|
||||
|
|
|
@ -461,9 +461,10 @@ impl<'bump> DiffMachine<'bump> {
|
|||
let mut please_commit = |edits: &mut Vec<DomEdit>| {
|
||||
if !has_comitted {
|
||||
has_comitted = true;
|
||||
if let Some(root) = root {
|
||||
edits.push(PushRoot { id: root.as_u64() });
|
||||
}
|
||||
log::info!("planning on committing... {:#?}, {:#?}", old, new);
|
||||
edits.push(PushRoot {
|
||||
id: root.unwrap().as_u64(),
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -475,7 +476,12 @@ impl<'bump> DiffMachine<'bump> {
|
|||
// TODO: take a more efficient path than this
|
||||
if old.attributes.len() == new.attributes.len() {
|
||||
for (old_attr, new_attr) in old.attributes.iter().zip(new.attributes.iter()) {
|
||||
if old_attr.value != new_attr.value || old_attr.is_volatile {
|
||||
log::info!("checking attribute");
|
||||
if old_attr.value != new_attr.value {
|
||||
please_commit(&mut self.mutations.edits);
|
||||
self.mutations.set_attribute(new_attr);
|
||||
} else if new_attr.is_volatile {
|
||||
log::debug!("setting due to volatile atttributes");
|
||||
please_commit(&mut self.mutations.edits);
|
||||
self.mutations.set_attribute(new_attr);
|
||||
}
|
||||
|
|
|
@ -264,8 +264,10 @@ pub mod on {
|
|||
ClipboardEventInner(ClipboardEvent): [
|
||||
/// Called when "copy"
|
||||
oncopy
|
||||
|
||||
/// oncut
|
||||
oncut
|
||||
|
||||
/// onpaste
|
||||
onpaste
|
||||
];
|
||||
|
@ -305,7 +307,7 @@ pub mod on {
|
|||
/// onchange
|
||||
onchange
|
||||
|
||||
/// oninput
|
||||
/// oninput handler
|
||||
oninput
|
||||
|
||||
/// oninvalid
|
||||
|
@ -446,22 +448,31 @@ pub mod on {
|
|||
PointerEventInner(PointerEvent): [
|
||||
/// pointerdown
|
||||
onpointerdown
|
||||
|
||||
/// pointermove
|
||||
onpointermove
|
||||
|
||||
/// pointerup
|
||||
onpointerup
|
||||
|
||||
/// pointercancel
|
||||
onpointercancel
|
||||
|
||||
/// gotpointercapture
|
||||
ongotpointercapture
|
||||
|
||||
/// lostpointercapture
|
||||
onlostpointercapture
|
||||
|
||||
/// pointerenter
|
||||
onpointerenter
|
||||
|
||||
/// pointerleave
|
||||
onpointerleave
|
||||
|
||||
/// pointerover
|
||||
onpointerover
|
||||
|
||||
/// pointerout
|
||||
onpointerout
|
||||
];
|
||||
|
@ -474,10 +485,13 @@ pub mod on {
|
|||
TouchEventInner(TouchEvent): [
|
||||
/// ontouchcancel
|
||||
ontouchcancel
|
||||
|
||||
/// ontouchend
|
||||
ontouchend
|
||||
|
||||
/// ontouchmove
|
||||
ontouchmove
|
||||
|
||||
/// ontouchstart
|
||||
ontouchstart
|
||||
];
|
||||
|
@ -490,48 +504,70 @@ pub mod on {
|
|||
MediaEventInner(MediaEvent): [
|
||||
///abort
|
||||
onabort
|
||||
|
||||
///canplay
|
||||
oncanplay
|
||||
|
||||
///canplaythrough
|
||||
oncanplaythrough
|
||||
|
||||
///durationchange
|
||||
ondurationchange
|
||||
|
||||
///emptied
|
||||
onemptied
|
||||
|
||||
///encrypted
|
||||
onencrypted
|
||||
|
||||
///ended
|
||||
onended
|
||||
|
||||
///error
|
||||
onerror
|
||||
|
||||
///loadeddata
|
||||
onloadeddata
|
||||
|
||||
///loadedmetadata
|
||||
onloadedmetadata
|
||||
|
||||
///loadstart
|
||||
onloadstart
|
||||
|
||||
///pause
|
||||
onpause
|
||||
|
||||
///play
|
||||
onplay
|
||||
|
||||
///playing
|
||||
onplaying
|
||||
|
||||
///progress
|
||||
onprogress
|
||||
|
||||
///ratechange
|
||||
onratechange
|
||||
|
||||
///seeked
|
||||
onseeked
|
||||
|
||||
///seeking
|
||||
onseeking
|
||||
|
||||
///stalled
|
||||
onstalled
|
||||
|
||||
///suspend
|
||||
onsuspend
|
||||
|
||||
///timeupdate
|
||||
ontimeupdate
|
||||
|
||||
///volumechange
|
||||
onvolumechange
|
||||
|
||||
///waiting
|
||||
onwaiting
|
||||
];
|
||||
|
@ -539,8 +575,10 @@ pub mod on {
|
|||
AnimationEventInner(AnimationEvent): [
|
||||
/// onanimationstart
|
||||
onanimationstart
|
||||
|
||||
/// onanimationend
|
||||
onanimationend
|
||||
|
||||
/// onanimationiteration
|
||||
onanimationiteration
|
||||
];
|
||||
|
|
|
@ -197,6 +197,21 @@ pub struct VElement<'a> {
|
|||
pub children: &'a [VNode<'a>],
|
||||
}
|
||||
|
||||
impl Debug for VElement<'_> {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||
f.debug_struct("VElement")
|
||||
.field("tag_name", &self.tag_name)
|
||||
.field("namespace", &self.namespace)
|
||||
.field("key", &self.key)
|
||||
.field("dom_id", &self.dom_id)
|
||||
.field("parent_id", &self.parent_id)
|
||||
.field("listeners", &self.listeners.len())
|
||||
.field("attributes", &self.attributes)
|
||||
.field("children", &self.children)
|
||||
.finish()
|
||||
}
|
||||
}
|
||||
|
||||
/// A trait for any generic Dioxus Element.
|
||||
///
|
||||
/// This trait provides the ability to use custom elements in the `rsx!` macro.
|
||||
|
@ -429,11 +444,6 @@ impl<'a> NodeFactory<'a> {
|
|||
is_volatile: bool,
|
||||
) -> Attribute<'a> {
|
||||
let (value, is_static) = self.raw_text(val);
|
||||
let is_volatile = match name {
|
||||
"value" | "checked" | "selected" => true,
|
||||
_ => false,
|
||||
};
|
||||
|
||||
Attribute {
|
||||
name,
|
||||
value,
|
||||
|
|
|
@ -72,6 +72,7 @@ use crate::heuristics::*;
|
|||
use crate::innerlude::*;
|
||||
use futures_channel::mpsc::{UnboundedReceiver, UnboundedSender};
|
||||
use futures_util::{pin_mut, stream::FuturesUnordered, Future, FutureExt, StreamExt};
|
||||
use fxhash::FxHashMap;
|
||||
use fxhash::FxHashSet;
|
||||
use indexmap::IndexSet;
|
||||
use slab::Slab;
|
||||
|
@ -357,6 +358,8 @@ impl Scheduler {
|
|||
let shared = self.pool.clone();
|
||||
let mut machine = unsafe { saved_state.promote(&shared) };
|
||||
|
||||
let mut ran_scopes = FxHashSet::default();
|
||||
|
||||
if machine.stack.is_empty() {
|
||||
let shared = self.pool.clone();
|
||||
|
||||
|
@ -367,11 +370,18 @@ impl Scheduler {
|
|||
});
|
||||
|
||||
if let Some(scopeid) = self.dirty_scopes.pop() {
|
||||
let component = self.pool.get_scope(scopeid).unwrap();
|
||||
let (old, new) = (component.frames.wip_head(), component.frames.fin_head());
|
||||
// let (old, new) = (component.frames.wip_head(), component.frames.fin_head());
|
||||
machine.stack.scope_stack.push(scopeid);
|
||||
machine.stack.push(DiffInstruction::Diff { new, old });
|
||||
log::info!("handlng dirty scope {:#?}", scopeid);
|
||||
if !ran_scopes.contains(&scopeid) {
|
||||
ran_scopes.insert(scopeid);
|
||||
|
||||
let mut component = self.pool.get_scope_mut(scopeid).unwrap();
|
||||
if component.run_scope(&self.pool) {
|
||||
let (old, new) = (component.frames.wip_head(), component.frames.fin_head());
|
||||
// let (old, new) = (component.frames.wip_head(), component.frames.fin_head());
|
||||
machine.stack.scope_stack.push(scopeid);
|
||||
machine.stack.push(DiffInstruction::Diff { new, old });
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -472,15 +482,7 @@ impl Scheduler {
|
|||
while let Ok(Some(dirty_scope)) = self.receiver.try_next() {
|
||||
match dirty_scope {
|
||||
SchedulerMsg::Immediate(im) => {
|
||||
log::debug!("Handling immediate {:?}", im);
|
||||
|
||||
if let Some(scope) = self.pool.get_scope_mut(im) {
|
||||
if scope.run_scope(&self.pool) {
|
||||
self.dirty_scopes.insert(im);
|
||||
} else {
|
||||
todo!()
|
||||
}
|
||||
}
|
||||
self.dirty_scopes.insert(im);
|
||||
}
|
||||
SchedulerMsg::UiEvent(e) => self.ui_events.push_back(e),
|
||||
SchedulerMsg::Task(_) => todo!(),
|
||||
|
|
|
@ -3,7 +3,6 @@ use dioxus_core as dioxus;
|
|||
use dioxus_html as dioxus_elements;
|
||||
|
||||
static Parent: FC<()> = |cx, props| {
|
||||
//
|
||||
let value = cx.use_hook(|_| String::new(), |f| &*f, |_| {});
|
||||
|
||||
cx.render(rsx! {
|
||||
|
|
|
@ -49,9 +49,9 @@ use std::{
|
|||
/// }
|
||||
/// }
|
||||
/// ```
|
||||
pub fn use_state<'a, 'c, T: 'static, F: FnOnce() -> T>(
|
||||
pub fn use_state<'a, 'c, T: 'static>(
|
||||
cx: Context<'a>,
|
||||
initial_state_fn: F,
|
||||
initial_state_fn: impl FnOnce() -> T,
|
||||
) -> UseState<'a, T> {
|
||||
cx.use_hook(
|
||||
move |_| UseStateInner {
|
||||
|
|
|
@ -6,7 +6,7 @@ edition = "2018"
|
|||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
dioxus-core = { path="../core" }
|
||||
dioxus-core = { path = "../core" }
|
||||
|
||||
[dev-dependencies]
|
||||
scraper = "0.12.0"
|
||||
|
|
|
@ -681,7 +681,10 @@ macro_rules! builder_constructors {
|
|||
$(
|
||||
$(#[$attr:meta])*
|
||||
$name:ident {
|
||||
$($fil:ident: $vil:ident,)*
|
||||
$(
|
||||
$(#[$attr_method:meta])*
|
||||
$fil:ident: $vil:ident,
|
||||
)*
|
||||
};
|
||||
)*
|
||||
) => {
|
||||
|
@ -699,6 +702,7 @@ macro_rules! builder_constructors {
|
|||
|
||||
impl $name {
|
||||
$(
|
||||
$(#[$attr_method])*
|
||||
pub fn $fil<'a>(&self, cx: NodeFactory<'a>, val: Arguments) -> Attribute<'a> {
|
||||
cx.attr(stringify!($fil), val, None, false)
|
||||
}
|
||||
|
@ -1525,7 +1529,10 @@ builder_constructors! {
|
|||
src: Uri,
|
||||
step: String,
|
||||
tabindex: usize,
|
||||
r#type: InputType,
|
||||
|
||||
// This has a manual implementation below
|
||||
// r#type: InputType,
|
||||
|
||||
value: String,
|
||||
width: isize,
|
||||
};
|
||||
|
@ -1570,7 +1577,10 @@ builder_constructors! {
|
|||
option {
|
||||
disabled: Bool,
|
||||
label: String,
|
||||
selected: Bool,
|
||||
|
||||
// defined below
|
||||
// selected: Bool,
|
||||
|
||||
value: String,
|
||||
};
|
||||
|
||||
|
@ -1595,7 +1605,8 @@ builder_constructors! {
|
|||
/// [`<select>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/select)
|
||||
/// element.
|
||||
select {
|
||||
value: String,
|
||||
// defined below
|
||||
// value: String,
|
||||
autocomplete: String,
|
||||
autofocus: Bool,
|
||||
disabled: Bool,
|
||||
|
@ -1656,6 +1667,54 @@ builder_constructors! {
|
|||
template {};
|
||||
}
|
||||
|
||||
impl input {
|
||||
/// The type of input
|
||||
///
|
||||
/// Here are the different input types you can use in HTML:
|
||||
///
|
||||
/// - `button`
|
||||
/// - `checkbox`
|
||||
/// - `color`
|
||||
/// - `date`
|
||||
/// - `datetime-local`
|
||||
/// - `email`
|
||||
/// - `file`
|
||||
/// - `hidden`
|
||||
/// - `image`
|
||||
/// - `month`
|
||||
/// - `number`
|
||||
/// - `password`
|
||||
/// - `radio`
|
||||
/// - `range`
|
||||
/// - `reset`
|
||||
/// - `search`
|
||||
/// - `submit`
|
||||
/// - `tel`
|
||||
/// - `text`
|
||||
/// - `time`
|
||||
/// - `url`
|
||||
/// - `week`
|
||||
pub fn r#type<'a>(&self, cx: NodeFactory<'a>, val: Arguments) -> Attribute<'a> {
|
||||
cx.attr("type", val, None, false)
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
volatile attributes
|
||||
*/
|
||||
|
||||
impl select {
|
||||
pub fn value<'a>(&self, cx: NodeFactory<'a>, val: Arguments) -> Attribute<'a> {
|
||||
cx.attr("value", val, None, true)
|
||||
}
|
||||
}
|
||||
|
||||
impl option {
|
||||
fn selected<'a>(&self, cx: NodeFactory<'a>, val: Arguments) -> Attribute<'a> {
|
||||
cx.attr("selected", val, None, true)
|
||||
}
|
||||
}
|
||||
|
||||
pub trait SvgAttributes {
|
||||
aria_trait_methods! {
|
||||
accent_height: "accent-height",
|
||||
|
|
6
packages/web/BUGS.md
Normal file
6
packages/web/BUGS.md
Normal file
|
@ -0,0 +1,6 @@
|
|||
# Known quirks for browsers and their workarounds
|
||||
|
||||
- text merging (solved through comment nodes)
|
||||
- cursor jumping to end on inputs (not yet solved, solved in React already)
|
||||
- SVG attributes cannot be set (solved using the correct method)
|
||||
- volatile components
|
|
@ -11,7 +11,7 @@ license = "MIT/Apache-2.0"
|
|||
dioxus-core = { path = "../core", version = "0.1.2" }
|
||||
dioxus-html = { path = "../html" }
|
||||
js-sys = "0.3"
|
||||
wasm-bindgen = { version = "0.2.71", features = ["enable-interning"] }
|
||||
wasm-bindgen = { version = "0.2.78", features = ["enable-interning"] }
|
||||
lazy_static = "1.4.0"
|
||||
wasm-bindgen-futures = "0.4.20"
|
||||
log = "0.4.14"
|
||||
|
@ -70,12 +70,12 @@ crate-type = ["cdylib", "rlib"]
|
|||
|
||||
[dev-dependencies]
|
||||
im-rc = "15.0.0"
|
||||
# rand = { version="0.8.4", features=["small_rng"] }
|
||||
separator = "0.4.1"
|
||||
uuid = { version = "0.8.2", features = ["v4", "wasm-bindgen"] }
|
||||
dioxus-hooks = { path = "../hooks" }
|
||||
serde = { version = "1.0.126", features = ["derive"] }
|
||||
surf = { git = "https://github.com/http-rs/surf", rev = "1ffaba8873", default-features = false, features = [
|
||||
"wasm-client",
|
||||
] }
|
||||
# wasm-timer = "0.2.5"
|
||||
|
||||
# rand = { version="0.8.4", features=["small_rng"] }
|
||||
# surf = { version = "2.3.1", default-features = false, features = [
|
||||
# "wasm-client",
|
||||
# ] }
|
||||
|
|
|
@ -15,7 +15,7 @@ use std::{pin::Pin, time::Duration};
|
|||
|
||||
fn main() {
|
||||
// Setup logging
|
||||
// wasm_logger::init(wasm_logger::Config::new(log::Level::Debug));
|
||||
wasm_logger::init(wasm_logger::Config::new(log::Level::Debug));
|
||||
console_error_panic_hook::set_once();
|
||||
|
||||
// Run the app
|
||||
|
@ -24,32 +24,44 @@ fn main() {
|
|||
|
||||
static APP: FC<()> = |cx, props| {
|
||||
let mut count = use_state(cx, || 3);
|
||||
let mut content = use_state(cx, || String::from("h1"));
|
||||
let mut text_content = use_state(cx, || String::from("Hello, world!"));
|
||||
|
||||
log::debug!("running scope...");
|
||||
|
||||
let mut content = use_state(cx, || String::new());
|
||||
cx.render(rsx! {
|
||||
div {
|
||||
h1 { "content val is {content}" }
|
||||
|
||||
input {
|
||||
value: "{content}"
|
||||
oninput: move |e| {
|
||||
content.set(e.value());
|
||||
}
|
||||
}
|
||||
button {
|
||||
onclick: move |_| count += 1,
|
||||
"Click to add."
|
||||
"Current count: {count}"
|
||||
r#type: "text",
|
||||
value: "{text_content}"
|
||||
oninput: move |e| text_content.set(e.value())
|
||||
}
|
||||
|
||||
br {}
|
||||
{(0..10).map(|f| {
|
||||
rsx!(
|
||||
button {
|
||||
onclick: move |_| count += 1,
|
||||
"Click to add."
|
||||
"Current count: {count}"
|
||||
}
|
||||
br {}
|
||||
)
|
||||
})}
|
||||
|
||||
select {
|
||||
name: "cars"
|
||||
id: "cars"
|
||||
value: "h1"
|
||||
value: "{content}"
|
||||
oninput: move |ev| {
|
||||
content.set(ev.value());
|
||||
match ev.value().as_str() {
|
||||
"h1" => count.set(0),
|
||||
"h2" => count.set(5),
|
||||
"h3" => count.set(10),
|
||||
s => {}
|
||||
_ => {}
|
||||
}
|
||||
},
|
||||
|
||||
|
@ -58,14 +70,7 @@ static APP: FC<()> = |cx, props| {
|
|||
option { value: "h3", "h3" }
|
||||
}
|
||||
|
||||
ul {
|
||||
{(0..*count).map(|f| rsx!{
|
||||
li { "a - {f}" }
|
||||
li { "b - {f}" }
|
||||
li { "c - {f}" }
|
||||
})}
|
||||
|
||||
}
|
||||
{render_list(cx, *count)}
|
||||
|
||||
{render_bullets(cx)}
|
||||
|
||||
|
@ -80,4 +85,19 @@ fn render_bullets(cx: Context) -> DomTree {
|
|||
})
|
||||
}
|
||||
|
||||
static Child: FC<()> = |cx, props| rsx!(cx, div {"hello child"});
|
||||
fn render_list(cx: Context, count: usize) -> DomTree {
|
||||
let items = (0..count).map(|f| {
|
||||
rsx! {
|
||||
li { "a - {f}" }
|
||||
li { "b - {f}" }
|
||||
li { "c - {f}" }
|
||||
}
|
||||
});
|
||||
|
||||
rsx!(cx, ul { {items} })
|
||||
}
|
||||
|
||||
static Child: FC<()> = |cx, props| {
|
||||
// render
|
||||
rsx!(cx, div {"hello child"})
|
||||
};
|
||||
|
|
|
@ -24,7 +24,7 @@ fn main() {
|
|||
dioxus_web::launch(App, |c| c)
|
||||
}
|
||||
|
||||
static App: FC<()> = |cx, props|{
|
||||
static App: FC<()> = |cx, props| {
|
||||
let mut state = use_state(cx, || 0);
|
||||
cx.render(rsx! {
|
||||
div {
|
||||
|
|
|
@ -314,12 +314,20 @@ impl WebsysDom {
|
|||
}
|
||||
}
|
||||
|
||||
if let Some(node) = node.dyn_ref::<HtmlInputElement>() {
|
||||
if let Some(input) = node.dyn_ref::<HtmlInputElement>() {
|
||||
if name == "value" {
|
||||
node.set_value(value);
|
||||
/*
|
||||
if the attribute being set is the same as the value of the input, then don't bother setting it.
|
||||
This is used in controlled components to keep the cursor in the right spot.
|
||||
|
||||
this logic should be moved into the virtualdom since we have the notion of "volatile"
|
||||
*/
|
||||
if input.value() != value {
|
||||
input.set_value(value);
|
||||
}
|
||||
}
|
||||
if name == "checked" {
|
||||
node.set_checked(true);
|
||||
input.set_checked(true);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -156,6 +156,7 @@ pub async fn run_with_props<T: Properties + 'static>(root: FC<T>, root_props: T,
|
|||
work_loop.wait_for_raf().await;
|
||||
|
||||
for mut edit in mutations {
|
||||
log::debug!("edits are {:#?}", edit);
|
||||
// actually apply our changes during the animation frame
|
||||
websys_dom.process_edits(&mut edit.edits);
|
||||
}
|
||||
|
|
|
@ -4,7 +4,7 @@ use gloo_timers::future::TimeoutFuture;
|
|||
use js_sys::Function;
|
||||
use wasm_bindgen::JsCast;
|
||||
use wasm_bindgen::{prelude::Closure, JsValue};
|
||||
use web_sys::Window;
|
||||
use web_sys::{window, Window};
|
||||
|
||||
pub struct RafLoop {
|
||||
window: Window,
|
||||
|
@ -24,10 +24,21 @@ impl RafLoop {
|
|||
|
||||
let (ric_sender, ric_receiver) = async_channel::unbounded();
|
||||
|
||||
let ric_closure: Closure<dyn Fn(JsValue)> = Closure::wrap(Box::new(move |_v: JsValue| {
|
||||
//
|
||||
let deadline = _v.dyn_into::<web_sys::IdleDeadline>().unwrap();
|
||||
let time_remaining = deadline.time_remaining() as u32;
|
||||
let has_idle_callback = {
|
||||
let bo = window().unwrap().dyn_into::<js_sys::Object>().unwrap();
|
||||
bo.has_own_property(&JsValue::from_str("requestIdleCallback"))
|
||||
};
|
||||
let ric_closure: Closure<dyn Fn(JsValue)> = Closure::wrap(Box::new(move |v: JsValue| {
|
||||
let time_remaining = if has_idle_callback {
|
||||
if let Ok(deadline) = v.dyn_into::<web_sys::IdleDeadline>() {
|
||||
deadline.time_remaining() as u32
|
||||
} else {
|
||||
10
|
||||
}
|
||||
} else {
|
||||
10
|
||||
};
|
||||
|
||||
ric_sender.try_send(time_remaining).unwrap()
|
||||
}));
|
||||
|
||||
|
|
Loading…
Reference in a new issue