소스 검색

polish: examples

Jonathan Kelley 3 년 전
부모
커밋
1a2f91e

+ 3 - 1
Cargo.toml

@@ -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"

+ 26 - 24
examples/async.rs

@@ -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();
             }
         }
     })

+ 15 - 17
examples/borrowed.rs

@@ -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::*;
 

+ 55 - 73
examples/calculator.rs

@@ -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(), "●" }
-
-                    {(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() "=" }
-                }
+    use separator::Separatable;
+    let formatted_display = cur_val.separated_string();
+
+    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()}
     })
 }

+ 10 - 4
packages/core/src/diff.rs

@@ -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);
                 }

+ 39 - 1
packages/core/src/events.rs

@@ -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
         ];

+ 15 - 5
packages/core/src/nodes.rs

@@ -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,

+ 16 - 14
packages/core/src/scheduler.rs

@@ -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!(),

+ 0 - 1
packages/core/tests/borrowedstate.rs

@@ -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! {

+ 2 - 2
packages/hooks/src/usestate.rs

@@ -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 {

+ 1 - 1
packages/html/Cargo.toml

@@ -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"

+ 63 - 4
packages/html/src/lib.rs

@@ -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 - 0
packages/web/BUGS.md

@@ -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

+ 6 - 6
packages/web/Cargo.toml

@@ -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",
+# ] }

+ 42 - 22
packages/web/examples/basic.rs

@@ -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"})
+};

+ 1 - 1
packages/web/examples/blah.rs

@@ -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 {

+ 11 - 3
packages/web/src/dom.rs

@@ -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);
             }
         }
 

+ 1 - 0
packages/web/src/lib.rs

@@ -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);
         }

+ 16 - 5
packages/web/src/ric_raf.rs

@@ -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()
         }));