polish: examples

This commit is contained in:
Jonathan Kelley committed 2021-09-24 00:05:56 -04:00
1 parent 4b6ca05f2c
commit 1a2f91ed91
19 files changed
+327 -183

No files matched your search

+3 -1
View File
@@ -51,8 +51,10 @@ argh = "0.1.5"
env_logger = "*" env_logger = "*"
async-std = { version = "1.9.0", features = ["attributes"] } async-std = { version = "1.9.0", features = ["attributes"] }
rand = { version = "0.8.4", features = ["small_rng"] } 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" gloo-timers = "0.2.1"
# surf = { version = "2.3.1", default-features = false, features = [
# "wasm-client",
# ] }
[target.'cfg(target_arch = "wasm32")'.dev-dependencies] [target.'cfg(target_arch = "wasm32")'.dev-dependencies]
gloo-timers = "0.2.1" gloo-timers = "0.2.1"
+26 -24
View File
@@ -1,41 +1,43 @@
//! Example: README.md showcase /*
//! This example shows how to use async and loops to implement a coroutine in a component. Coroutines can be controlled via
//! The example from the README.md the `TaskHandle` object.
*/
use dioxus::prelude::*; use dioxus::prelude::*;
use gloo_timers::future::TimeoutFuture;
fn main() { 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 count = use_state(cx, || 0);
let mut direction = use_state(cx, || 1); let mut direction = use_state(cx, || 1);
let (async_count, dir) = (count.for_async(), *direction); 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 { loop {
gloo_timers::future::TimeoutFuture::new(250).await; TimeoutFuture::new(250).await;
*async_count.get_mut() += dir; *async_count.get_mut() += dir;
} }
}); });
cx.render(rsx! { rsx!(cx, div {
div { h1 {"count is {count}"}
h1 {"count is {count}"} button {
button { "Stop counting"
"Stop counting" onclick: move |_| task.stop()
onclick: move |_| task.stop() }
} button {
button { "Start counting"
"Start counting" onclick: move |_| task.resume()
onclick: move |_| task.resume() }
} button {
button { "Switch counting direcion"
"Switch counting direcion" onclick: move |_| {
onclick: move |_| { direction *= -1;
direction *= -1; task.restart();
task.restart();
}
} }
} }
}) })
+15 -17
View File
@@ -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.
//!
//! Dioxus manages borrow lifetimes for you. This means any child may borrow from its parent. However, it is not possible How does it work?
//! to hand out an &mut T to children - all props are consumed by &P, so you'd only get an &&mut T.
//! Dioxus will manually drop closures and props - things that borrow data before the component is ran again. This is done
//! How does it work? "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.
//! 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 We cannot drop from the parent to the children - if the drop implementation modifies the data, downstream references
//! drop implementation is manually called, freeing any memory and ensuring that memory is not leaked. 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,
//! We cannot drop from the parent to the children - if the drop implementation modifies the data, downstream references and is proven to be safe with MIRI.
//! 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::*; use dioxus::prelude::*;
+54 -72
View File
@@ -1,25 +1,19 @@
//! Example: Calculator /*
//! ------------------------- 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.
fn main() { */
env_logger::init();
dioxus::desktop::launch(App, |cfg| cfg).unwrap();
}
use dioxus::events::on::*; use dioxus::events::on::*;
use dioxus::prelude::*; use dioxus::prelude::*;
enum Operator { fn main() {
Add, dioxus::desktop::launch(APP, |cfg| cfg).unwrap();
Sub,
Mul,
Div,
} }
const App: FC<()> = |cx, props| { const APP: FC<()> = |cx, _| {
let cur_val = use_state(cx, || 0.0_f64); let cur_val = use_state(cx, || 0.0_f64);
let operator = use_state(cx, || None as Option<Operator>); let operator = use_state(cx, || None);
let display_value = use_state(cx, || "".to_string()); let display_value = use_state(cx, || String::from(""));
let clear_display = display_value == "0"; let clear_display = display_value == "0";
let clear_text = if clear_display { "C" } else { "AC" }; 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() { let keydownhandler = move |evt: KeyboardEvent| match evt.key_code() {
KeyCode::Backspace => { KeyCode::Add => operator.set(Some(Operator::Add)),
if !display_value.as_str().eq("0") { KeyCode::Subtract => operator.set(Some(Operator::Sub)),
display_value.modify().pop(); KeyCode::Divide => operator.set(Some(Operator::Div)),
} KeyCode::Multiply => operator.set(Some(Operator::Mul)),
}
KeyCode::Num0 => input_digit(0), KeyCode::Num0 => input_digit(0),
KeyCode::Num1 => input_digit(1), KeyCode::Num1 => input_digit(1),
KeyCode::Num2 => input_digit(2), KeyCode::Num2 => input_digit(2),
@@ -77,73 +70,62 @@ const App: FC<()> = |cx, props| {
KeyCode::Num7 => input_digit(7), KeyCode::Num7 => input_digit(7),
KeyCode::Num8 => input_digit(8), KeyCode::Num8 => input_digit(8),
KeyCode::Num9 => input_digit(9), KeyCode::Num9 => input_digit(9),
KeyCode::Add => operator.set(Some(Operator::Add)), KeyCode::Backspace => {
KeyCode::Subtract => operator.set(Some(Operator::Sub)), if !display_value.as_str().eq("0") {
KeyCode::Divide => operator.set(Some(Operator::Div)), display_value.modify().pop();
KeyCode::Multiply => operator.set(Some(Operator::Mul)), }
}
_ => {} _ => {}
}; };
cx.render(rsx! { use separator::Separatable;
div { class: "calculator", onkeydown: {keydownhandler} let formatted_display = cur_val.separated_string();
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!{ rsx!(cx, div {
CalculatorKey { key: "{k}", name: "key-{k}", onclick: move |_| input_digit(k), "{k}" } class: "calculator", onkeydown: {keydownhandler}
})} div { class: "calculator-display", "{formatted_display}" }
} div { class: "input-keys"
div { class: "operator-keys" div { class: "function-keys"
CalculatorKey { name: "key-divide", onclick: move |_| operator.set(Some(Operator::Div)) "÷" } CalculatorKey { name: "key-clear", onclick: {clear_key} "{clear_text}" }
CalculatorKey { name: "key-multiply", onclick: move |_| operator.set(Some(Operator::Mul)) "×" } CalculatorKey { name: "key-sign", onclick: {toggle_sign}, "±"}
CalculatorKey { name: "key-subtract", onclick: move |_| operator.set(Some(Operator::Sub)) "−" } CalculatorKey { name: "key-percent", onclick: {toggle_percent} "%"}
CalculatorKey { name: "key-add", onclick: move |_| operator.set(Some(Operator::Add)) "+" } }
CalculatorKey { name: "key-equals", onclick: move |_| perform_operation() "=" } 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)] #[derive(Props)]
struct CalculatorKeyProps<'a> { struct CalculatorKeyProps<'a> {
/// Name!
name: &'static str, name: &'static str,
/// Click!
onclick: &'a dyn Fn(MouseEvent), onclick: &'a dyn Fn(MouseEvent),
} }
fn CalculatorKey<'a>(cx: Context<'a>, props: &'a CalculatorKeyProps) -> DomTree<'a> { fn CalculatorKey<'a>(cx: Context<'a>, props: &'a CalculatorKeyProps) -> DomTree<'a> {
cx.render(rsx! { rsx!(cx, button {
button { class: "calculator-key {props.name}"
class: "calculator-key {props.name}" onclick: {props.onclick}
onclick: {props.onclick} {cx.children()}
{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}"
}
}) })
} }
+10 -4
View File
@@ -461,9 +461,10 @@ impl<'bump> DiffMachine<'bump> {
let mut please_commit = |edits: &mut Vec<DomEdit>| { let mut please_commit = |edits: &mut Vec<DomEdit>| {
if !has_comitted { if !has_comitted {
has_comitted = true; has_comitted = true;
if let Some(root) = root { log::info!("planning on committing... {:#?}, {:#?}", old, new);
edits.push(PushRoot { id: root.as_u64() }); edits.push(PushRoot {
} id: root.unwrap().as_u64(),
});
} }
}; };
@@ -475,7 +476,12 @@ impl<'bump> DiffMachine<'bump> {
// TODO: take a more efficient path than this // TODO: take a more efficient path than this
if old.attributes.len() == new.attributes.len() { if old.attributes.len() == new.attributes.len() {
for (old_attr, new_attr) in old.attributes.iter().zip(new.attributes.iter()) { 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); please_commit(&mut self.mutations.edits);
self.mutations.set_attribute(new_attr); self.mutations.set_attribute(new_attr);
} }
+39 -1
View File
@@ -264,8 +264,10 @@ pub mod on {
ClipboardEventInner(ClipboardEvent): [ ClipboardEventInner(ClipboardEvent): [
/// Called when "copy" /// Called when "copy"
oncopy oncopy
/// oncut /// oncut
oncut oncut
/// onpaste /// onpaste
onpaste onpaste
]; ];
@@ -305,7 +307,7 @@ pub mod on {
/// onchange /// onchange
onchange onchange
/// oninput /// oninput handler
oninput oninput
/// oninvalid /// oninvalid
@@ -446,22 +448,31 @@ pub mod on {
PointerEventInner(PointerEvent): [ PointerEventInner(PointerEvent): [
/// pointerdown /// pointerdown
onpointerdown onpointerdown
/// pointermove /// pointermove
onpointermove onpointermove
/// pointerup /// pointerup
onpointerup onpointerup
/// pointercancel /// pointercancel
onpointercancel onpointercancel
/// gotpointercapture /// gotpointercapture
ongotpointercapture ongotpointercapture
/// lostpointercapture /// lostpointercapture
onlostpointercapture onlostpointercapture
/// pointerenter /// pointerenter
onpointerenter onpointerenter
/// pointerleave /// pointerleave
onpointerleave onpointerleave
/// pointerover /// pointerover
onpointerover onpointerover
/// pointerout /// pointerout
onpointerout onpointerout
]; ];
@@ -474,10 +485,13 @@ pub mod on {
TouchEventInner(TouchEvent): [ TouchEventInner(TouchEvent): [
/// ontouchcancel /// ontouchcancel
ontouchcancel ontouchcancel
/// ontouchend /// ontouchend
ontouchend ontouchend
/// ontouchmove /// ontouchmove
ontouchmove ontouchmove
/// ontouchstart /// ontouchstart
ontouchstart ontouchstart
]; ];
@@ -490,48 +504,70 @@ pub mod on {
MediaEventInner(MediaEvent): [ MediaEventInner(MediaEvent): [
///abort ///abort
onabort onabort
///canplay ///canplay
oncanplay oncanplay
///canplaythrough ///canplaythrough
oncanplaythrough oncanplaythrough
///durationchange ///durationchange
ondurationchange ondurationchange
///emptied ///emptied
onemptied onemptied
///encrypted ///encrypted
onencrypted onencrypted
///ended ///ended
onended onended
///error ///error
onerror onerror
///loadeddata ///loadeddata
onloadeddata onloadeddata
///loadedmetadata ///loadedmetadata
onloadedmetadata onloadedmetadata
///loadstart ///loadstart
onloadstart onloadstart
///pause ///pause
onpause onpause
///play ///play
onplay onplay
///playing ///playing
onplaying onplaying
///progress ///progress
onprogress onprogress
///ratechange ///ratechange
onratechange onratechange
///seeked ///seeked
onseeked onseeked
///seeking ///seeking
onseeking onseeking
///stalled ///stalled
onstalled onstalled
///suspend ///suspend
onsuspend onsuspend
///timeupdate ///timeupdate
ontimeupdate ontimeupdate
///volumechange ///volumechange
onvolumechange onvolumechange
///waiting ///waiting
onwaiting onwaiting
]; ];
@@ -539,8 +575,10 @@ pub mod on {
AnimationEventInner(AnimationEvent): [ AnimationEventInner(AnimationEvent): [
/// onanimationstart /// onanimationstart
onanimationstart onanimationstart
/// onanimationend /// onanimationend
onanimationend onanimationend
/// onanimationiteration /// onanimationiteration
onanimationiteration onanimationiteration
]; ];
+15 -5
View File
@@ -197,6 +197,21 @@ pub struct VElement<'a> {
pub children: &'a [VNode<'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. /// A trait for any generic Dioxus Element.
/// ///
/// This trait provides the ability to use custom elements in the `rsx!` macro. /// This trait provides the ability to use custom elements in the `rsx!` macro.
@@ -429,11 +444,6 @@ impl<'a> NodeFactory<'a> {
is_volatile: bool, is_volatile: bool,
) -> Attribute<'a> { ) -> Attribute<'a> {
let (value, is_static) = self.raw_text(val); let (value, is_static) = self.raw_text(val);
let is_volatile = match name {
"value" | "checked" | "selected" => true,
_ => false,
};
Attribute { Attribute {
name, name,
value, value,
+16 -14
View File
@@ -72,6 +72,7 @@ use crate::heuristics::*;
use crate::innerlude::*; use crate::innerlude::*;
use futures_channel::mpsc::{UnboundedReceiver, UnboundedSender}; use futures_channel::mpsc::{UnboundedReceiver, UnboundedSender};
use futures_util::{pin_mut, stream::FuturesUnordered, Future, FutureExt, StreamExt}; use futures_util::{pin_mut, stream::FuturesUnordered, Future, FutureExt, StreamExt};
use fxhash::FxHashMap;
use fxhash::FxHashSet; use fxhash::FxHashSet;
use indexmap::IndexSet; use indexmap::IndexSet;
use slab::Slab; use slab::Slab;
@@ -357,6 +358,8 @@ impl Scheduler {
let shared = self.pool.clone(); let shared = self.pool.clone();
let mut machine = unsafe { saved_state.promote(&shared) }; let mut machine = unsafe { saved_state.promote(&shared) };
let mut ran_scopes = FxHashSet::default();
if machine.stack.is_empty() { if machine.stack.is_empty() {
let shared = self.pool.clone(); let shared = self.pool.clone();
@@ -367,11 +370,18 @@ impl Scheduler {
}); });
if let Some(scopeid) = self.dirty_scopes.pop() { if let Some(scopeid) = self.dirty_scopes.pop() {
let component = self.pool.get_scope(scopeid).unwrap(); log::info!("handlng dirty scope {:#?}", scopeid);
let (old, new) = (component.frames.wip_head(), component.frames.fin_head()); if !ran_scopes.contains(&scopeid) {
// let (old, new) = (component.frames.wip_head(), component.frames.fin_head()); ran_scopes.insert(scopeid);
machine.stack.scope_stack.push(scopeid);
machine.stack.push(DiffInstruction::Diff { new, old }); 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() { while let Ok(Some(dirty_scope)) = self.receiver.try_next() {
match dirty_scope { match dirty_scope {
SchedulerMsg::Immediate(im) => { SchedulerMsg::Immediate(im) => {
log::debug!("Handling immediate {:?}", im); self.dirty_scopes.insert(im);
if let Some(scope) = self.pool.get_scope_mut(im) {
if scope.run_scope(&self.pool) {
self.dirty_scopes.insert(im);
} else {
todo!()
}
}
} }
SchedulerMsg::UiEvent(e) => self.ui_events.push_back(e), SchedulerMsg::UiEvent(e) => self.ui_events.push_back(e),
SchedulerMsg::Task(_) => todo!(), SchedulerMsg::Task(_) => todo!(),
-1
View File
@@ -3,7 +3,6 @@ use dioxus_core as dioxus;
use dioxus_html as dioxus_elements; use dioxus_html as dioxus_elements;
static Parent: FC<()> = |cx, props| { static Parent: FC<()> = |cx, props| {
//
let value = cx.use_hook(|_| String::new(), |f| &*f, |_| {}); let value = cx.use_hook(|_| String::new(), |f| &*f, |_| {});
cx.render(rsx! { cx.render(rsx! {
+2 -2
View File
@@ -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>, cx: Context<'a>,
initial_state_fn: F, initial_state_fn: impl FnOnce() -> T,
) -> UseState<'a, T> { ) -> UseState<'a, T> {
cx.use_hook( cx.use_hook(
move |_| UseStateInner { move |_| UseStateInner {
+1 -1
View File
@@ -6,7 +6,7 @@ edition = "2018"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies] [dependencies]
dioxus-core = { path="../core" } dioxus-core = { path = "../core" }
[dev-dependencies] [dev-dependencies]
scraper = "0.12.0" scraper = "0.12.0"
+63 -4
View File
@@ -681,7 +681,10 @@ macro_rules! builder_constructors {
$( $(
$(#[$attr:meta])* $(#[$attr:meta])*
$name:ident { $name:ident {
$($fil:ident: $vil:ident,)* $(
$(#[$attr_method:meta])*
$fil:ident: $vil:ident,
)*
}; };
)* )*
) => { ) => {
@@ -699,6 +702,7 @@ macro_rules! builder_constructors {
impl $name { impl $name {
$( $(
$(#[$attr_method])*
pub fn $fil<'a>(&self, cx: NodeFactory<'a>, val: Arguments) -> Attribute<'a> { pub fn $fil<'a>(&self, cx: NodeFactory<'a>, val: Arguments) -> Attribute<'a> {
cx.attr(stringify!($fil), val, None, false) cx.attr(stringify!($fil), val, None, false)
} }
@@ -1525,7 +1529,10 @@ builder_constructors! {
src: Uri, src: Uri,
step: String, step: String,
tabindex: usize, tabindex: usize,
r#type: InputType,
// This has a manual implementation below
// r#type: InputType,
value: String, value: String,
width: isize, width: isize,
}; };
@@ -1570,7 +1577,10 @@ builder_constructors! {
option { option {
disabled: Bool, disabled: Bool,
label: String, label: String,
selected: Bool,
// defined below
// selected: Bool,
value: String, value: String,
}; };
@@ -1595,7 +1605,8 @@ builder_constructors! {
/// [`<select>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/select) /// [`<select>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/select)
/// element. /// element.
select { select {
value: String, // defined below
// value: String,
autocomplete: String, autocomplete: String,
autofocus: Bool, autofocus: Bool,
disabled: Bool, disabled: Bool,
@@ -1656,6 +1667,54 @@ builder_constructors! {
template {}; 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 { pub trait SvgAttributes {
aria_trait_methods! { aria_trait_methods! {
accent_height: "accent-height", accent_height: "accent-height",
+6
View 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
+6 -6
View File
@@ -11,7 +11,7 @@ license = "MIT/Apache-2.0"
dioxus-core = { path = "../core", version = "0.1.2" } dioxus-core = { path = "../core", version = "0.1.2" }
dioxus-html = { path = "../html" } dioxus-html = { path = "../html" }
js-sys = "0.3" 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" lazy_static = "1.4.0"
wasm-bindgen-futures = "0.4.20" wasm-bindgen-futures = "0.4.20"
log = "0.4.14" log = "0.4.14"
@@ -70,12 +70,12 @@ crate-type = ["cdylib", "rlib"]
[dev-dependencies] [dev-dependencies]
im-rc = "15.0.0" im-rc = "15.0.0"
# rand = { version="0.8.4", features=["small_rng"] }
separator = "0.4.1" separator = "0.4.1"
uuid = { version = "0.8.2", features = ["v4", "wasm-bindgen"] } uuid = { version = "0.8.2", features = ["v4", "wasm-bindgen"] }
dioxus-hooks = { path = "../hooks" } dioxus-hooks = { path = "../hooks" }
serde = { version = "1.0.126", features = ["derive"] } serde = { version = "1.0.126", features = ["derive"] }
surf = { git = "https://github.com/http-rs/surf", rev = "1ffaba8873", default-features = false, features = [
"wasm-client", # rand = { version="0.8.4", features=["small_rng"] }
] } # surf = { version = "2.3.1", default-features = false, features = [
# wasm-timer = "0.2.5" # "wasm-client",
# ] }
+42 -22
View File
@@ -15,7 +15,7 @@ use std::{pin::Pin, time::Duration};
fn main() { fn main() {
// Setup logging // 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(); console_error_panic_hook::set_once();
// Run the app // Run the app
@@ -24,32 +24,44 @@ fn main() {
static APP: FC<()> = |cx, props| { static APP: FC<()> = |cx, props| {
let mut count = use_state(cx, || 3); 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! { cx.render(rsx! {
div { div {
h1 { "content val is {content}" }
input { input {
value: "{content}" r#type: "text",
oninput: move |e| { value: "{text_content}"
content.set(e.value()); oninput: move |e| text_content.set(e.value())
}
}
button {
onclick: move |_| count += 1,
"Click to add."
"Current count: {count}"
} }
br {}
{(0..10).map(|f| {
rsx!(
button {
onclick: move |_| count += 1,
"Click to add."
"Current count: {count}"
}
br {}
)
})}
select { select {
name: "cars" name: "cars"
id: "cars" id: "cars"
value: "h1" value: "{content}"
oninput: move |ev| { oninput: move |ev| {
content.set(ev.value());
match ev.value().as_str() { match ev.value().as_str() {
"h1" => count.set(0), "h1" => count.set(0),
"h2" => count.set(5), "h2" => count.set(5),
"h3" => count.set(10), "h3" => count.set(10),
s => {} _ => {}
} }
}, },
@@ -58,14 +70,7 @@ static APP: FC<()> = |cx, props| {
option { value: "h3", "h3" } option { value: "h3", "h3" }
} }
ul { {render_list(cx, *count)}
{(0..*count).map(|f| rsx!{
li { "a - {f}" }
li { "b - {f}" }
li { "c - {f}" }
})}
}
{render_bullets(cx)} {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
View File
@@ -24,7 +24,7 @@ fn main() {
dioxus_web::launch(App, |c| c) dioxus_web::launch(App, |c| c)
} }
static App: FC<()> = |cx, props|{ static App: FC<()> = |cx, props| {
let mut state = use_state(cx, || 0); let mut state = use_state(cx, || 0);
cx.render(rsx! { cx.render(rsx! {
div { div {
+11 -3
View File
@@ -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" { 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" { if name == "checked" {
node.set_checked(true); input.set_checked(true);
} }
} }
+1
View File
@@ -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; work_loop.wait_for_raf().await;
for mut edit in mutations { for mut edit in mutations {
log::debug!("edits are {:#?}", edit);
// actually apply our changes during the animation frame // actually apply our changes during the animation frame
websys_dom.process_edits(&mut edit.edits); websys_dom.process_edits(&mut edit.edits);
} }
+16 -5
View File
@@ -4,7 +4,7 @@ use gloo_timers::future::TimeoutFuture;
use js_sys::Function; use js_sys::Function;
use wasm_bindgen::JsCast; use wasm_bindgen::JsCast;
use wasm_bindgen::{prelude::Closure, JsValue}; use wasm_bindgen::{prelude::Closure, JsValue};
use web_sys::Window; use web_sys::{window, Window};
pub struct RafLoop { pub struct RafLoop {
window: Window, window: Window,
@@ -24,10 +24,21 @@ impl RafLoop {
let (ric_sender, ric_receiver) = async_channel::unbounded(); let (ric_sender, ric_receiver) = async_channel::unbounded();
let ric_closure: Closure<dyn Fn(JsValue)> = Closure::wrap(Box::new(move |_v: JsValue| { let has_idle_callback = {
// let bo = window().unwrap().dyn_into::<js_sys::Object>().unwrap();
let deadline = _v.dyn_into::<web_sys::IdleDeadline>().unwrap(); bo.has_own_property(&JsValue::from_str("requestIdleCallback"))
let time_remaining = deadline.time_remaining() as u32; };
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() ric_sender.try_send(time_remaining).unwrap()
})); }));