From 402fef7f76f186ea11a37950f6c621844a26f3b6 Mon Sep 17 00:00:00 2001 From: tiye Date: Mon, 29 Jul 2024 20:26:05 +0800 Subject: [PATCH 1/6] fix input.value set; tag 0.1.10 --- Cargo.lock | 2 +- respo/Cargo.toml | 2 +- respo/src/app/renderer.rs | 27 ++++++++++++++++----------- 3 files changed, 18 insertions(+), 13 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index af16292..0f6237b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -195,7 +195,7 @@ dependencies = [ [[package]] name = "respo" -version = "0.1.9" +version = "0.1.10" dependencies = [ "cirru_parser", "js-sys", diff --git a/respo/Cargo.toml b/respo/Cargo.toml index da1351e..87ea7ce 100644 --- a/respo/Cargo.toml +++ b/respo/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "respo" -version = "0.1.9" +version = "0.1.10" edition = "2021" description = "a tiny virtual DOM library migrated from ClojureScript" license = "Apache-2.0" diff --git a/respo/src/app/renderer.rs b/respo/src/app/renderer.rs index fd13885..494d5fa 100644 --- a/respo/src/app/renderer.rs +++ b/respo/src/app/renderer.rs @@ -10,7 +10,7 @@ use std::sync::RwLock; use wasm_bindgen::{JsCast, JsValue}; use web_sys::console::{error_1, warn_1}; -use web_sys::{HtmlElement, HtmlLabelElement, Node}; +use web_sys::{HtmlElement, HtmlLabelElement, HtmlTextAreaElement, Node}; use crate::app::diff::{collect_effects_outside_in_as, diff_tree}; use crate::app::patch::{attach_event, patch_tree}; @@ -240,16 +240,21 @@ where let element = document.create_element(name)?; for (key, value) in attrs { let key = key.as_ref(); - if key == "style" { - warn_1(&"style is handled outside attrs".into()); - } else if key == "innerText" { - element.dyn_ref::().expect("into html element").set_inner_text(value); - } else if key == "innerHTML" { - element.set_inner_html(value); - } else if key == "htmlFor" { - element.dyn_ref::().ok_or("to label element")?.set_html_for(value); - } else { - element.set_attribute(key, value)?; + match key { + "style" => warn_1(&"style is handled outside attrs".into()), + "innerText" => element.dyn_ref::().expect("into html element").set_inner_text(value), + "innerHTML" => element.set_inner_html(value), + "htmlFor" => element + .dyn_ref::() + .expect("into label element") + .set_html_for(value), + "value" if &**name == "textarea" || &**name == "input" => element + .dyn_ref::() + .expect("into html element") + .set_value(value), + _ => { + element.set_attribute(key, value)?; + } } } if !style.is_empty() { From bf8c7bd1c8584bc16d770340a3a53feeb5ba1026 Mon Sep 17 00:00:00 2001 From: tiye Date: Tue, 30 Jul 2024 15:53:28 +0800 Subject: [PATCH 2/6] refine error logs; tag 0.1.11 --- Cargo.lock | 2 +- respo/Cargo.toml | 2 +- respo/src/app.rs | 6 ++++-- respo/src/app/patch.rs | 6 +++--- respo/src/app/renderer.rs | 14 ++++++-------- respo/src/app/util.rs | 21 +++++++++++++++++++-- respo/src/ui/dialog.rs | 4 ++-- respo/src/ui/dialog/confirm.rs | 2 +- respo/src/ui/dialog/prompt.rs | 2 +- 9 files changed, 38 insertions(+), 21 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 0f6237b..f4c7c99 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -195,7 +195,7 @@ dependencies = [ [[package]] name = "respo" -version = "0.1.10" +version = "0.1.11" dependencies = [ "cirru_parser", "js-sys", diff --git a/respo/Cargo.toml b/respo/Cargo.toml index 87ea7ce..4128ea6 100644 --- a/respo/Cargo.toml +++ b/respo/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "respo" -version = "0.1.10" +version = "0.1.11" edition = "2021" description = "a tiny virtual DOM library migrated from ClojureScript" license = "Apache-2.0" diff --git a/respo/src/app.rs b/respo/src/app.rs index 6f8d56a..65c75e5 100644 --- a/respo/src/app.rs +++ b/respo/src/app.rs @@ -82,7 +82,9 @@ pub trait RespoApp { DispatchFn::new(dispatch_action), Self::get_loop_delay(), ) - .expect("rendering node"); + .unwrap_or_else(|e| { + util::error_log!("render loop error: {:?}", e); + }); Ok(()) } @@ -123,7 +125,7 @@ pub trait RespoApp { *store.borrow_mut() = s; } Err(e) => { - util::log!("error: {:?}", e); + util::error_log!("error: {:?}", e); } }, _ => { diff --git a/respo/src/app/patch.rs b/respo/src/app/patch.rs index e108c99..b5878dc 100644 --- a/respo/src/app/patch.rs +++ b/respo/src/app/patch.rs @@ -59,7 +59,7 @@ where } } } else { - crate::util::log!("expected component for effects, got: {}", target_tree); + crate::util::warn_log!("expected component for effects, got: {}", target_tree); } } } @@ -243,7 +243,7 @@ where } } } else { - crate::util::log!("expected component for effects, got: {}", target_tree); + crate::util::warn_log!("expected component for effects, got: {}", target_tree); } } } @@ -272,7 +272,7 @@ where } } } else { - crate::util::log!("expected component for effects, got: {}", target_tree); + crate::util::warn_log!("expected component for effects, got: {}", target_tree); } } } diff --git a/respo/src/app/renderer.rs b/respo/src/app/renderer.rs index 494d5fa..623a3a8 100644 --- a/respo/src/app/renderer.rs +++ b/respo/src/app/renderer.rs @@ -25,8 +25,8 @@ fn drain_rerender_status() -> bool { let ret = { *NEED_TO_ERENDER.read().expect("to drain rerender status") }; if ret { - let mut need_to_erender = NEED_TO_ERENDER.write().expect("to drain rerender status"); - *need_to_erender = false; + let mut need_to_rerender = NEED_TO_ERENDER.write().expect("to drain rerender status"); + *need_to_rerender = false; } ret } @@ -108,17 +108,15 @@ where let mut changes: Vec> = vec![]; diff_tree(&new_tree, &to_prev_tree.borrow(), &Vec::new(), &Vec::new(), &mut changes)?; + // use cirru_parser::CirruWriterOptions; // util::log!( // "prev tree: {}", - // cirru_parser::format( - // &[to_prev_tree2.borrow().to_owned().into()], - // cirru_parser::CirruWriterOptions { use_inline: true } - // ) - // .unwrap() + // cirru_parser::format(&[to_prev_tree.borrow().to_owned().into()], CirruWriterOptions { use_inline: true }).unwrap() // ); + // use crate::dom_change::changes_to_cirru; // util::log!( // "changes: {}", - // cirru_parser::format(&[changes_to_cirru(&changes)], cirru_parser::CirruWriterOptions { use_inline: true }).unwrap() + // cirru_parser::format(&[changes_to_cirru(&changes)], CirruWriterOptions { use_inline: true }).unwrap() // ); let handler = handle_event.to_owned(); diff --git a/respo/src/app/util.rs b/respo/src/app/util.rs index ab12183..1a1c203 100644 --- a/respo/src/app/util.rs +++ b/respo/src/app/util.rs @@ -40,7 +40,10 @@ pub fn raf_loop_slow(interval: i32, mut cb: Box Result<(), String *g.borrow_mut() = Some(Closure::wrap(Box::new(move || { if let Err(e) = cb() { - crate::log!("failed in slow loop: {}", e); + crate::warn_log!( + "Failure in slow loop, program has to stop since inconsistent states. Details: {}", + e + ); } let h = Closure::wrap(Box::new({ @@ -95,7 +98,7 @@ macro_rules! log { /// /// use it like: /// ```ignore -/// util::warn!("a is {}", a); +/// util::warn_log!("a is {}", a); /// ``` #[macro_export] macro_rules! warn_log { @@ -104,6 +107,20 @@ macro_rules! warn_log { }}; } +/// wraps on top of `web_sys::console.error_1`. +/// +/// use it like: +/// ```ignore +/// util::error_log!("a is {}", a); +/// ``` +#[macro_export] +macro_rules! error_log { + ($($t:tt)*) => {{ + web_sys::console::error_1(&format!($($t)*).into()); + }}; +} + +pub use error_log; pub use log; pub use warn_log; diff --git a/respo/src/ui/dialog.rs b/respo/src/ui/dialog.rs index 9d7da40..80fa865 100644 --- a/respo/src/ui/dialog.rs +++ b/respo/src/ui/dialog.rs @@ -105,7 +105,7 @@ impl RespoEffect for EffectModalFade { delay_call.forget(); } None => { - util::log!("content not found"); + util::warn_log!("content not found"); } } } @@ -181,7 +181,7 @@ impl RespoEffect for EffectDrawerFade { delay_call.forget(); } None => { - app::util::log!("content not found"); + app::util::warn_log!("content not found"); } } } diff --git a/respo/src/ui/dialog/confirm.rs b/respo/src/ui/dialog/confirm.rs index 4596d2f..60c4591 100644 --- a/respo/src/ui/dialog/confirm.rs +++ b/respo/src/ui/dialog/confirm.rs @@ -230,7 +230,7 @@ where let window = web_sys::window().unwrap(); // dirty global variable to store a shared callback if let Err(e) = Reflect::set(&window, &JsValue::from_str(NEXT_TASK_NAME), task.as_ref()) { - app::util::log!("failed to store next task {:?}", e); + app::util::error_log!("failed to store next task {:?}", e); } task.forget(); dispatch.run_state(&self.cursor, s)?; diff --git a/respo/src/ui/dialog/prompt.rs b/respo/src/ui/dialog/prompt.rs index 2cf0e8c..8155cac 100644 --- a/respo/src/ui/dialog/prompt.rs +++ b/respo/src/ui/dialog/prompt.rs @@ -348,7 +348,7 @@ where let window = web_sys::window().unwrap(); // dirty global variable to store a shared callback if let Err(e) = Reflect::set(&window, &JsValue::from_str(NEXT_TASK_NAME), task.as_ref()) { - util::log!("failed to store next task {:?}", e); + util::error_log!("failed to store next task {:?}", e); } task.forget(); dispatch.run_state(&self.cursor, s)?; From 7880f6b54cc470a7705cdcb9b74e0ad4ac1f9fb8 Mon Sep 17 00:00:00 2001 From: tiye Date: Tue, 30 Jul 2024 17:01:56 +0800 Subject: [PATCH 3/6] temp fix on some event binding; tag 0.1.12 --- Cargo.lock | 2 +- respo/Cargo.toml | 2 +- respo/src/app/patch.rs | 98 +++++++++++++++++++++++++++++++++--------- 3 files changed, 80 insertions(+), 22 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index f4c7c99..e60f01b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -195,7 +195,7 @@ dependencies = [ [[package]] name = "respo" -version = "0.1.11" +version = "0.1.12" dependencies = [ "cirru_parser", "js-sys", diff --git a/respo/Cargo.toml b/respo/Cargo.toml index 4128ea6..aef374b 100644 --- a/respo/Cargo.toml +++ b/respo/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "respo" -version = "0.1.11" +version = "0.1.12" edition = "2021" description = "a tiny virtual DOM library migrated from ClojureScript" license = "Apache-2.0" diff --git a/respo/src/app/patch.rs b/respo/src/app/patch.rs index b5878dc..1e42d86 100644 --- a/respo/src/app/patch.rs +++ b/respo/src/app/patch.rs @@ -13,6 +13,7 @@ use web_sys::console::warn_1; use crate::node::{RespoComponent, RespoEffectType, RespoEvent, RespoEventMark, RespoEventMarkFn, RespoNode}; use super::renderer::load_coord_target_tree; +use super::util; use crate::node::dom_change::{ChildDomOp, DomChange, RespoCoord}; use crate::app::renderer::build_dom_tree; @@ -386,11 +387,25 @@ pub fn attach_event(element: &Element, key: &str, coord: &[RespoCoord], handle_e .run(RespoEventMark::new("change", &coord, wrap_event)) .expect("handle change event"); }) as Box); - element - .dyn_ref::() - .expect("convert to html input element") - .set_onchange(Some(handler.as_ref().unchecked_ref())); - handler.forget(); + match element.tag_name().as_str() { + "INPUT" => { + element + .dyn_ref::() + .expect("convert to html input element") + .set_onchange(Some(handler.as_ref().unchecked_ref())); + handler.forget(); + } + "TEXTAREA" => { + element + .dyn_ref::() + .expect("convert to html input element") + .set_onchange(Some(handler.as_ref().unchecked_ref())); + handler.forget(); + } + _ => { + util::warn_log!("not handled change event for element: {}", element.tag_name()); + } + } } "keydown" => { let handler = Closure::wrap(Box::new(move |e: KeyboardEvent| { @@ -409,11 +424,26 @@ pub fn attach_event(element: &Element, key: &str, coord: &[RespoCoord], handle_e .run(RespoEventMark::new("keydown", &coord, wrap_event)) .expect("handle keydown event"); }) as Box); - element - .dyn_ref::() - .expect("convert to html input element") - .set_onkeydown(Some(handler.as_ref().unchecked_ref())); - handler.forget(); + + match element.tag_name().as_str() { + "INPUT" => { + element + .dyn_ref::() + .expect("convert to html input element") + .set_onkeydown(Some(handler.as_ref().unchecked_ref())); + handler.forget(); + } + "TEXTAREA" => { + element + .dyn_ref::() + .expect("convert to html input element") + .set_onkeydown(Some(handler.as_ref().unchecked_ref())); + handler.forget(); + } + _ => { + util::warn_log!("not handled keydown event for element: {}", element.tag_name()); + } + } } "keyup" => { let handler = Closure::wrap(Box::new(move |e: KeyboardEvent| { @@ -431,11 +461,25 @@ pub fn attach_event(element: &Element, key: &str, coord: &[RespoCoord], handle_e .run(RespoEventMark::new("keyup", &coord, wrap_event)) .expect("handle keyup event"); }) as Box); - element - .dyn_ref::() - .expect("convert to html input element") - .set_onkeyup(Some(handler.as_ref().unchecked_ref())); - handler.forget(); + match element.tag_name().as_str() { + "INPUT" => { + element + .dyn_ref::() + .expect("convert to html input element") + .set_onkeyup(Some(handler.as_ref().unchecked_ref())); + handler.forget(); + } + "TEXTAREA" => { + element + .dyn_ref::() + .expect("convert to html input element") + .set_onkeyup(Some(handler.as_ref().unchecked_ref())); + handler.forget(); + } + _ => { + util::warn_log!("not handled keyup event for element: {}", element.tag_name()); + } + } } "keypress" => { let handler = Closure::wrap(Box::new(move |e: KeyboardEvent| { @@ -453,11 +497,25 @@ pub fn attach_event(element: &Element, key: &str, coord: &[RespoCoord], handle_e .run(RespoEventMark::new("keypress", &coord, wrap_event)) .expect("handle keypress event"); }) as Box); - element - .dyn_ref::() - .expect("convert to html input element") - .set_onkeypress(Some(handler.as_ref().unchecked_ref())); - handler.forget(); + match element.tag_name().as_str() { + "INPUT" => { + element + .dyn_ref::() + .expect("convert to html input element") + .set_onkeypress(Some(handler.as_ref().unchecked_ref())); + handler.forget(); + } + "TEXTAREA" => { + element + .dyn_ref::() + .expect("convert to html input element") + .set_onkeypress(Some(handler.as_ref().unchecked_ref())); + handler.forget(); + } + _ => { + util::warn_log!("not handled keypress event for element: {}", element.tag_name()); + } + } } "focus" => { let handler = Closure::wrap(Box::new(move |e: FocusEvent| { From c6b0d90251003672f96fef918b3232b434fd6e85 Mon Sep 17 00:00:00 2001 From: tiye Date: Sat, 3 Aug 2024 19:54:24 +0800 Subject: [PATCH 4/6] fix innerText issues in diffing; tag 0.1.13 --- Cargo.lock | 2 +- demo_respo/src/inner_text.rs | 57 ++++++++++++++++++++++++++++++++++++ demo_respo/src/main.rs | 9 +++++- respo/Cargo.toml | 2 +- respo/src/app/diff.rs | 26 ++++++++++++++-- respo/src/app/patch.rs | 5 +++- respo/src/app/renderer.rs | 26 ++++++++++++---- respo/src/app/util.rs | 2 +- 8 files changed, 117 insertions(+), 12 deletions(-) create mode 100644 demo_respo/src/inner_text.rs diff --git a/Cargo.lock b/Cargo.lock index e60f01b..e9df6b4 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -195,7 +195,7 @@ dependencies = [ [[package]] name = "respo" -version = "0.1.12" +version = "0.1.13" dependencies = [ "cirru_parser", "js-sys", diff --git a/demo_respo/src/inner_text.rs b/demo_respo/src/inner_text.rs new file mode 100644 index 0000000..9414170 --- /dev/null +++ b/demo_respo/src/inner_text.rs @@ -0,0 +1,57 @@ +//! a demo for switching inner-text and children, which might cause a bug in respo + +use std::fmt::Debug; + +use respo::{button, css::RespoStyle, div, span, ui::ui_button, util, DispatchFn, RespoElement, RespoEvent}; +use respo_state_derive::RespoState; +use serde::{Deserialize, Serialize}; + +use respo::states_tree::{RespoState, RespoStatesTree}; + +use super::store::ActionOp; + +#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize, RespoState)] +struct InnerTextState { + inner_text: bool, +} + +pub fn comp_inner_text(states: &RespoStatesTree) -> Result, String> { + let cursor = states.path(); + + let state = states.cast_branch::(); + + let on_inc = { + let cursor = cursor.to_owned(); + let state = state.to_owned(); + move |e, dispatch: DispatchFn<_>| -> Result<(), String> { + util::log!("click {:?}", e); + if let RespoEvent::Click { original_event, .. } = e { + original_event.prevent_default(); + } + + dispatch.run(ActionOp::Increment)?; + dispatch.run_state( + &cursor, + InnerTextState { + inner_text: !state.inner_text, + }, + )?; + Ok(()) + } + }; + + Ok( + div().elements([ + div().elements([button() + .class(ui_button()) + .inner_text("Switch inner text") + .style(RespoStyle::default().margin(4.)) + .on_click(on_inc)]), + div().elements([if state.inner_text { + div().inner_text("inner text") + } else { + div().elements([span().inner_text("child 1"), span().inner_text("child 2")]) + }]), + ]), + ) +} diff --git a/demo_respo/src/main.rs b/demo_respo/src/main.rs index 7f3fb87..1afeb31 100644 --- a/demo_respo/src/main.rs +++ b/demo_respo/src/main.rs @@ -1,6 +1,7 @@ extern crate console_error_panic_hook; mod counter; +mod inner_text; mod panel; mod plugins; mod store; @@ -11,7 +12,8 @@ use std::cell::{Ref, RefCell}; use std::panic; use std::rc::Rc; -use respo::RespoAction; +use inner_text::comp_inner_text; +use respo::{space, RespoAction}; use web_sys::Node; use respo::ui::ui_global; @@ -65,9 +67,14 @@ impl RespoApp for App { .style(RespoStyle::default().padding(12.0)) .children([ comp_counter(&states.pick("counter"), store.counted)?.to_node(), + space(None, Some(80)).to_node(), comp_panel(&states.pick("panel"))?, comp_todolist(&states.pick("todolist"), &store.tasks)?.to_node(), + space(None, Some(80)).to_node(), comp_plugins_demo(&states.pick("plugins-demo"))?.to_node(), + space(None, Some(80)).to_node(), + comp_inner_text(&states.pick("inner-text"))?.to_node(), + space(None, Some(80)).to_node(), ]) .to_node(), ) diff --git a/respo/Cargo.toml b/respo/Cargo.toml index aef374b..c517db4 100644 --- a/respo/Cargo.toml +++ b/respo/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "respo" -version = "0.1.12" +version = "0.1.13" edition = "2021" description = "a tiny virtual DOM library migrated from ClojureScript" license = "Apache-2.0" diff --git a/respo/src/app/diff.rs b/respo/src/app/diff.rs index 273af4c..1fa99f2 100644 --- a/respo/src/app/diff.rs +++ b/respo/src/app/diff.rs @@ -1,3 +1,4 @@ +use std::cell::RefCell; use std::collections::{HashMap, HashSet}; use std::fmt::Debug; use std::rc::Rc; @@ -109,7 +110,8 @@ where }); collect_effects_outside_in_as(new_tree, coord, dom_path, RespoEffectType::Mounted, changes)?; } else { - diff_attrs(attrs, old_attrs, coord, dom_path, changes); + let reset_inner = RefCell::new(false); + diff_attrs(attrs, old_attrs, coord, dom_path, changes, &reset_inner); diff_style( &HashMap::from_iter(style.0.to_owned()), &HashMap::from_iter(old_style.0.to_owned()), @@ -119,7 +121,12 @@ where ); diff_event(event, old_event, coord, dom_path, changes); - diff_children(children, old_children, coord, dom_path, changes)?; + if *reset_inner.borrow() { + // children is empty after innerHTML or innerText changed + diff_children(children, &[], coord, dom_path, changes)?; + } else { + diff_children(children, old_children, coord, dom_path, changes)?; + } } } (RespoNode::Referenced(new_cell), RespoNode::Referenced(old_cell)) => { @@ -147,6 +154,7 @@ fn diff_attrs( coord: &[RespoCoord], dom_path: &[u32], changes: &mut Vec>, + reset_inner: &RefCell, ) where T: Debug + Clone, { @@ -156,15 +164,24 @@ fn diff_attrs( if old_attrs.contains_key(key) { if &old_attrs[key] != value { added.insert(key.to_owned(), value.to_owned()); + if inner_changed(key) { + *reset_inner.borrow_mut() = true; + } } } else { added.insert(key.to_owned(), value.to_owned()); + if inner_changed(key) { + *reset_inner.borrow_mut() = true; + } } } for key in old_attrs.keys() { if !new_attrs.contains_key(key) { removed.insert(key.to_owned()); + if inner_changed(key) { + *reset_inner.borrow_mut() = true; + } } } @@ -178,6 +195,11 @@ fn diff_attrs( } } +/// changed innerHTML or innerText, which resets children values +fn inner_changed(key: &Rc) -> bool { + key == &"innerHTML".into() || key == &"innerText".into() || key == &"inner-text".into() +} + fn diff_style( new_style: &HashMap, String>, old_style: &HashMap, String>, diff --git a/respo/src/app/patch.rs b/respo/src/app/patch.rs index 1e42d86..0a389ca 100644 --- a/respo/src/app/patch.rs +++ b/respo/src/app/patch.rs @@ -199,7 +199,10 @@ where .expect("get node") .children() .item(*idx) - .ok_or_else(|| format!("child to remove not found at {}", &idx))?; + .ok_or_else(|| { + util::warn_log!("child not found at {:?}", coord); + format!("child to remove not found at {}", &idx) + })?; target.remove_child(&child).expect("child removed"); } ChildDomOp::InsertAfter(idx, k, node) => { diff --git a/respo/src/app/renderer.rs b/respo/src/app/renderer.rs index 623a3a8..85af86a 100644 --- a/respo/src/app/renderer.rs +++ b/respo/src/app/renderer.rs @@ -3,6 +3,7 @@ use crate::node::dom_change::RespoCoord; use crate::node::{ DispatchFn, DomChange, RespoComponent, RespoEffectType, RespoElement, RespoEventMark, RespoEventMarkFn, RespoListenerFn, RespoNode, }; +use crate::warn_log; use std::cell::RefCell; use std::fmt::Debug; use std::rc::Rc; @@ -10,7 +11,7 @@ use std::sync::RwLock; use wasm_bindgen::{JsCast, JsValue}; use web_sys::console::{error_1, warn_1}; -use web_sys::{HtmlElement, HtmlLabelElement, HtmlTextAreaElement, Node}; +use web_sys::{HtmlElement, HtmlInputElement, HtmlLabelElement, HtmlTextAreaElement, Node}; use crate::app::diff::{collect_effects_outside_in_as, diff_tree}; use crate::app::patch::{attach_event, patch_tree}; @@ -236,20 +237,28 @@ where children, }) => { let element = document.create_element(name)?; + let mut inner_set = false; for (key, value) in attrs { let key = key.as_ref(); match key { "style" => warn_1(&"style is handled outside attrs".into()), - "innerText" => element.dyn_ref::().expect("into html element").set_inner_text(value), - "innerHTML" => element.set_inner_html(value), + "innerText" => { + inner_set = true; + element.dyn_ref::().expect("into html element").set_inner_text(value) + } + "innerHTML" => { + inner_set = true; + element.set_inner_html(value) + } "htmlFor" => element .dyn_ref::() .expect("into label element") .set_html_for(value), - "value" if &**name == "textarea" || &**name == "input" => element + "value" if &**name == "textarea" => element .dyn_ref::() - .expect("into html element") + .expect("into textarea element") .set_value(value), + "value" if &**name == "input" => element.dyn_ref::().expect("into input element").set_value(value), _ => { element.set_attribute(key, value)?; } @@ -258,6 +267,13 @@ where if !style.is_empty() { element.set_attribute("style", &style.to_string())?; } + if inner_set && !children.is_empty() { + warn_log!( + "innerText or innerHTML is set, it's conflicted with children: {} {:?}", + inner_set, + children + ); + } for (k, child) in children { let mut next_coord = coord.to_owned(); next_coord.push(RespoCoord::Key(k.to_owned())); diff --git a/respo/src/app/util.rs b/respo/src/app/util.rs index 1a1c203..94eab96 100644 --- a/respo/src/app/util.rs +++ b/respo/src/app/util.rs @@ -41,7 +41,7 @@ pub fn raf_loop_slow(interval: i32, mut cb: Box Result<(), String *g.borrow_mut() = Some(Closure::wrap(Box::new(move || { if let Err(e) = cb() { crate::warn_log!( - "Failure in slow loop, program has to stop since inconsistent states. Details: {}", + "Failure in slow loop, program has to stop since inconsistent DOM states. Details: {}", e ); } From 61001a36659dbea125f88089ae7a50f50b9bfdfd Mon Sep 17 00:00:00 2001 From: tiye Date: Sun, 4 Aug 2024 01:57:56 +0800 Subject: [PATCH 5/6] refactor on attr and attrs usages --- demo_respo/src/panel.rs | 3 +-- demo_respo/src/task.rs | 3 +-- respo/src/app/diff.rs | 4 ++-- respo/src/app/renderer.rs | 2 +- respo/src/node.rs | 2 +- respo/src/node/element.rs | 44 ++++++++++++++++++++++------------ respo/src/ui/dialog/alert.rs | 2 +- respo/src/ui/dialog/confirm.rs | 2 +- respo/src/ui/dialog/drawer.rs | 2 +- respo/src/ui/dialog/modal.rs | 2 +- respo/src/ui/dialog/prompt.rs | 5 ++-- 11 files changed, 41 insertions(+), 30 deletions(-) diff --git a/demo_respo/src/panel.rs b/demo_respo/src/panel.rs index 735bf56..343d2c4 100644 --- a/demo_respo/src/panel.rs +++ b/demo_respo/src/panel.rs @@ -65,9 +65,8 @@ pub fn comp_panel(states: &RespoStatesTree) -> Result, Strin "panel", div().elements([ input() + .attrs(&[("placeholder", "some content..."), ("value", state.content.as_str())]) .class(ui_input()) - .attribute("placeholder", "some content...") - .attribute("value", state.content.to_owned()) .on_input(on_input), space(Some(8), None), button().class(ui_button()).inner_text("add").on_click(on_submit), diff --git a/demo_respo/src/task.rs b/demo_respo/src/task.rs index b4d2b3e..bdfdd7c 100644 --- a/demo_respo/src/task.rs +++ b/demo_respo/src/task.rs @@ -102,9 +102,8 @@ pub fn comp_task( .on_click(on_remove), div().style(RespoStyle::default().margin4(0.0, 0.0, 0.0, 20.0)), input() + .attrs(&[("value", state.draft.as_str()), ("placeholder", "something to update...")]) .class(ui_input()) - .attribute("value", &state.draft) - .attribute("placeholder", "something to update...") .on_input(on_input), space(Some(8), None), button().class(ui_button()).inner_text("Update").on_click(on_update), diff --git a/respo/src/app/diff.rs b/respo/src/app/diff.rs index 1fa99f2..1cee155 100644 --- a/respo/src/app/diff.rs +++ b/respo/src/app/diff.rs @@ -88,14 +88,14 @@ where ( a @ RespoNode::Element(RespoElement { name, - attrs, + attributes: attrs, style, event, children, }), RespoNode::Element(RespoElement { name: old_name, - attrs: old_attrs, + attributes: old_attrs, style: old_style, event: old_event, children: old_children, diff --git a/respo/src/app/renderer.rs b/respo/src/app/renderer.rs index 85af86a..6682346 100644 --- a/respo/src/app/renderer.rs +++ b/respo/src/app/renderer.rs @@ -231,7 +231,7 @@ where } RespoNode::Element(RespoElement { name, - attrs, + attributes: attrs, style, event, children, diff --git a/respo/src/node.rs b/respo/src/node.rs index ca8712e..5664c46 100644 --- a/respo/src/node.rs +++ b/respo/src/node.rs @@ -123,7 +123,7 @@ where pub fn new_tag(name: &str) -> Self { Self::Element(RespoElement { name: name.into(), - attrs: HashMap::new(), + attributes: HashMap::new(), event: HashMap::new(), style: RespoStyle::default(), children: Vec::new(), diff --git a/respo/src/node/element.rs b/respo/src/node/element.rs index 4652a07..0be76dc 100644 --- a/respo/src/node/element.rs +++ b/respo/src/node/element.rs @@ -15,7 +15,7 @@ where { /// tagName pub name: Rc, - pub attrs: HashMap, String>, + pub attributes: HashMap, String>, pub(crate) event: HashMap, RespoListenerFn>, /// inlines styles, partially typed. /// there's also a macro called `static_styles` for inserting CSS rules @@ -38,7 +38,7 @@ where pub fn named(name: &str) -> Self { RespoElement { name: Rc::from(name), - attrs: HashMap::new(), + attributes: HashMap::new(), event: HashMap::new(), style: RespoStyle::default(), children: Vec::new(), @@ -77,29 +77,43 @@ where RespoElement { style, ..self } } /// set an attribute on element - pub fn attribute(self, property: U, value: V) -> Self + pub fn attr(self, property: U, value: V) -> Self where U: Into> + ToOwned, V: Display, { - let mut attrs = self.attrs.to_owned(); + let mut attrs = self.attributes.to_owned(); attrs.insert(property.into(), value.to_string()); - RespoElement { attrs, ..self.to_owned() } + RespoElement { + attributes: attrs, + ..self.to_owned() + } } /// set an attribute on element, but using `None` indicates noting - pub fn maybe_attribute(self, property: U, value: Option) -> Self + pub fn maybe_attr(self, property: U, value: Option) -> Self where U: Into> + ToOwned, V: Display, { if let Some(v) = value { - let mut attrs = self.attrs.to_owned(); + let mut attrs = self.attributes.to_owned(); attrs.insert(property.into(), v.to_string()); - RespoElement { attrs, ..self } + RespoElement { attributes: attrs, ..self } } else { self } } + /// set attributes from list of string pairs + pub fn attrs(self, list: &[(&str, V)]) -> Self + where + V: AsRef, + { + let mut attrs = self.attributes.to_owned(); + for (k, v) in list { + attrs.insert((*k).into(), v.as_ref().to_owned()); + } + RespoElement { attributes: attrs, ..self } + } pub fn on_click(self, handler: U) -> Self where U: Fn(RespoEvent, DispatchFn) -> Result<(), String> + 'static, @@ -185,7 +199,7 @@ where where U: Into, { - self.attribute("class", name.into()) + self.attr("class", name.into()) } /// attach an optional class name for adding styles pub fn maybe_class(self, name: Option) -> Self @@ -193,7 +207,7 @@ where U: Into, { match name { - Some(name) => self.attribute("class", name.into()), + Some(name) => self.attr("class", name.into()), None => self, } } @@ -203,7 +217,7 @@ where U: Into, { if on { - self.attribute("class", name.into()) + self.attr("class", name.into()) } else { self } @@ -217,27 +231,27 @@ where for name in names { class_name.push((*name).to_owned().into()); } - self.attribute("class", class_name.join(" ")) + self.attr("class", class_name.join(" ")) } /// writes `innerText` pub fn inner_text(self, content: U) -> Self where U: Into, { - self.attribute("innerText", content.into()) + self.attr("innerText", content.into()) } /// writes `innerHTML` pub fn inner_html(self, content: U) -> Self where U: Into, { - self.attribute("innerHTML", content.into()) + self.attr("innerHTML", content.into()) } /// writes `value` pub fn value(self, content: U) -> Self where U: Into, { - self.attribute("value", content.into()) + self.attr("value", content.into()) } } diff --git a/respo/src/ui/dialog/alert.rs b/respo/src/ui/dialog/alert.rs index 0fb8cb3..739ab88 100644 --- a/respo/src/ui/dialog/alert.rs +++ b/respo/src/ui/dialog/alert.rs @@ -95,7 +95,7 @@ where comp_esc_listener(show, close)?, ]) } else { - span().attribute("data-name", "placeholder") + span().attr("data-name", "placeholder") }]), ) .effect(EffectFocus { show }) diff --git a/respo/src/ui/dialog/confirm.rs b/respo/src/ui/dialog/confirm.rs index 60c4591..0bfe063 100644 --- a/respo/src/ui/dialog/confirm.rs +++ b/respo/src/ui/dialog/confirm.rs @@ -101,7 +101,7 @@ where comp_esc_listener(show, close)?, ]) } else { - span().attribute("data-name", "placeholder") + span().attr("data-name", "placeholder") }]), ) .effect(EffectFocus { show }) diff --git a/respo/src/ui/dialog/drawer.rs b/respo/src/ui/dialog/drawer.rs index adb2497..6dc3c4d 100644 --- a/respo/src/ui/dialog/drawer.rs +++ b/respo/src/ui/dialog/drawer.rs @@ -138,7 +138,7 @@ where comp_esc_listener(show, close)?, ]) } else { - span().attribute("data-name", "placeholder") + span().attr("data-name", "placeholder") }]), ) // .effect(&[show], effect_focus) diff --git a/respo/src/ui/dialog/modal.rs b/respo/src/ui/dialog/modal.rs index ac82347..375f425 100644 --- a/respo/src/ui/dialog/modal.rs +++ b/respo/src/ui/dialog/modal.rs @@ -140,7 +140,7 @@ where comp_esc_listener(show, close)?, ]) } else { - span().attribute("data-name", "placeholder") + span().attr("data-name", "placeholder") }]), ) // .effect(&[show], effect_focus) diff --git a/respo/src/ui/dialog/prompt.rs b/respo/src/ui/dialog/prompt.rs index 8155cac..1288521 100644 --- a/respo/src/ui/dialog/prompt.rs +++ b/respo/src/ui/dialog/prompt.rs @@ -192,10 +192,9 @@ where span().inner_text(options.text.unwrap_or_else(|| "Input your text:".to_owned())), space(None, Some(8)), div().elements([input_el + .attrs(&[("value", state.draft.as_str()), ("placeholder", "Content...")]) .class_list(&[ui_input()]) .style(RespoStyle::default().width(CssSize::Percent(100.0))) - .attribute("placeholder", "Content...") - .attribute("autoFocus", "autofocus") .value(state.draft.to_owned()) .on_input(on_text_input)]), match &state.error { @@ -218,7 +217,7 @@ where comp_esc_listener(show, close)?, ]) } else { - span().attribute("data-name", "placeholder") + span().attr("data-name", "placeholder") }]), ) // .effect(&[show], effect_focus) From 4daaacefcf94be44c6ebb6f6c4998ae676e8e0d5 Mon Sep 17 00:00:00 2001 From: tiye Date: Sun, 4 Aug 2024 03:01:46 +0800 Subject: [PATCH 6/6] syntax sugar for Respo styles; tag 0.1.14 --- Cargo.lock | 2 +- README.md | 14 ++-- demo_respo/src/counter.rs | 10 +-- demo_respo/src/inner_text.rs | 4 +- demo_respo/src/main.rs | 5 +- demo_respo/src/plugins.rs | 9 ++- demo_respo/src/task.rs | 36 ++++----- demo_respo/src/todolist.rs | 4 +- respo/Cargo.toml | 2 +- respo/src/node.rs | 5 +- respo/src/node/css.rs | 72 ++++++++---------- respo/src/node/css/css_size.rs | 74 ++++++++++++++++++ respo/src/node/element.rs | 6 +- respo/src/node/element/alias.rs | 26 +++---- respo/src/ui.rs | 100 ++++++++++++------------ respo/src/ui/dialog.rs | 52 +++++++------ respo/src/ui/dialog/alert.rs | 98 ++++++++++++------------ respo/src/ui/dialog/confirm.rs | 98 ++++++++++++------------ respo/src/ui/dialog/drawer.rs | 88 +++++++++++---------- respo/src/ui/dialog/modal.rs | 94 +++++++++++------------ respo/src/ui/dialog/prompt.rs | 130 ++++++++++++++++---------------- 21 files changed, 496 insertions(+), 433 deletions(-) create mode 100644 respo/src/node/css/css_size.rs diff --git a/Cargo.lock b/Cargo.lock index e9df6b4..8d9cfc5 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -195,7 +195,7 @@ dependencies = [ [[package]] name = "respo" -version = "0.1.13" +version = "0.1.14" dependencies = [ "cirru_parser", "js-sys", diff --git a/README.md b/README.md index 5a534ed..37cbf19 100644 --- a/README.md +++ b/README.md @@ -19,7 +19,7 @@ Here is some preview of DOM syntax: Ok( div() .class(ui_global()) - .style(RespoStyle::default().padding(12.0)) + .style(respo_style().padding(12.0)) .children([ comp_counter(&states.pick("counter"), store.counted)?, comp_panel(&states.pick("panel"))?, @@ -35,15 +35,15 @@ static_styles!( style_remove_button, ( "&", - RespoStyle::default() - .width(CssSize::Px(16.0)) - .height(CssSize::Px(16.0)) + respo_style() + .width(16.px()) + .height(16.px()) .margin(4.) - .cursor("pointer".to_owned()) + .cursor("pointer") .margin4(0.0, 0.0, 0.0, 16.0) .color(CssColor::Hsl(0, 90, 90)), ), - ("&:hover", RespoStyle::default().color(CssColor::Hsl(0, 90, 80))), + ("&:hover", respo_style().color(CssColor::Hsl(0, 90, 80))), ); ``` @@ -146,7 +146,7 @@ impl RespoApp for App { Ok( div() .class(ui_global()) - .style(RespoStyle::default().padding(12.0)) + .style(respo_style().padding(12.0)) .children([ comp_counter(&states.pick("counter"), store.counted)?, comp_panel(&states.pick("panel"))?, diff --git a/demo_respo/src/counter.rs b/demo_respo/src/counter.rs index 59b9f29..f119285 100644 --- a/demo_respo/src/counter.rs +++ b/demo_respo/src/counter.rs @@ -2,7 +2,7 @@ use std::fmt::Debug; use respo::{ br, button, - css::{CssColor, RespoStyle}, + css::{respo_style, CssColor}, div, span, ui::ui_button, util, DispatchFn, RespoElement, RespoEvent, @@ -85,21 +85,21 @@ pub fn comp_counter(states: &RespoStatesTree, global_counted: i32) -> Result Result Result| { @@ -116,7 +117,7 @@ pub fn comp_plugins_demo(states: &RespoStatesTree) -> Result Result Result Result`. +mod css_size; + use std::{ collections::HashSet, fmt::{self, Display, Formatter, Write}, @@ -25,10 +27,13 @@ use std::{ sync::RwLock, }; +use css_size::CssPx; use hsluv::hsluv_to_rgb; use wasm_bindgen::JsCast; use web_sys::Element; +pub use css_size::{ConvertRespoCssSize, CssSize}; + lazy_static::lazy_static! { static ref CLASS_NAME_IN_TAGS: RwLock> = RwLock::new(HashSet::new()); } @@ -40,6 +45,11 @@ lazy_static::lazy_static! { #[derive(Debug, Clone, PartialEq, Default, Eq)] pub struct RespoStyle(pub Vec<(Rc, String)>); +/// this is an alias +pub fn respo_style() -> RespoStyle { + RespoStyle::default() +} + impl RespoStyle { pub fn insert(self, property: &str, value: String) -> Self { let mut xs = self.0; @@ -81,17 +91,23 @@ impl RespoStyle { pub fn height(self, rule: CssSize) -> Self { self.insert("height", rule.to_string()) } - pub fn margin(self, m: f32) -> Self { - self.insert("margin", format!("{}px", m)) + pub fn margin>(self, m: T) -> Self { + self.insert("margin", format!("{}", m.into())) } - pub fn margin4(self, top: f32, right: f32, bottom: f32, left: f32) -> Self { - self.insert("margin", format!("{}px {}px {}px {}px", top, right, bottom, left)) + pub fn margin4>(self, top: T, right: T, bottom: T, left: T) -> Self { + self.insert( + "margin", + format!("{} {} {} {}", top.into(), right.into(), bottom.into(), left.into()), + ) } - pub fn padding(self, p: f32) -> Self { - self.insert("padding", format!("{}px", p)) + pub fn padding>(self, p: T) -> Self { + self.insert("padding", format!("{}", p.into())) } - pub fn padding4(self, top: f32, right: f32, bottom: f32, left: f32) -> Self { - self.insert("padding", format!("{}px {}px {}px {}px", top, right, bottom, left)) + pub fn padding4>(self, top: T, right: T, bottom: T, left: T) -> Self { + self.insert( + "padding", + format!("{} {} {} {}", top.into(), right.into(), bottom.into(), left.into()), + ) } pub fn border(self, rule: Option<(f32, CssBorderStyle, CssColor)>) -> Self { match rule { @@ -186,8 +202,8 @@ impl RespoStyle { pub fn text_overflow(self, overflow: CssTextOverflow) -> Self { self.insert("text-overflow", overflow.to_string()) } - pub fn cursor(self, cursor: String) -> Self { - self.insert("cursor", cursor) + pub fn cursor(self, cursor: &str) -> Self { + self.insert("cursor", cursor.to_owned()) } pub fn display(self, display: CssDisplay) -> Self { self.insert("display", display.to_string()) @@ -248,30 +264,6 @@ impl RespoStyle { } } -#[derive(Debug, Clone, PartialEq)] -pub enum CssSize { - Auto, - Px(f32), - Percent(f32), - Vw(f32), - Vh(f32), - /// may be calc or something - Custom(String), -} - -impl Display for CssSize { - fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { - match self { - Self::Auto => write!(f, "auto"), - Self::Px(v) => write!(f, "{}px", v), - Self::Percent(v) => write!(f, "{}%", v), - Self::Vw(v) => write!(f, "{}vw", v), - Self::Vh(v) => write!(f, "{}vh", v), - Self::Custom(v) => write!(f, "{}", v), - } - } -} - #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum CssPosition { Static, @@ -761,7 +753,7 @@ pub fn css_name_from_path(p: &str) -> String { /// ```rust /// respo::static_style_seq!(the_name, /// &[ -/// ("&", respo::css::RespoStyle::default()) +/// ("&", respo::css::respo_style()) /// ] /// ); /// ``` @@ -783,7 +775,7 @@ macro_rules! static_style_seq { /// macro to create a public function of CSS rules(up to 5 tuples) at current file scope, /// ```rust /// respo::static_styles!(the_name, -/// ("&", respo::css::RespoStyle::default()) +/// ("&", respo::css::respo_style()) /// ); /// ``` /// gets a function like: diff --git a/respo/src/node/css/css_size.rs b/respo/src/node/css/css_size.rs new file mode 100644 index 0000000..32dd66c --- /dev/null +++ b/respo/src/node/css/css_size.rs @@ -0,0 +1,74 @@ +use core::fmt; +use std::fmt::{Display, Formatter}; + +#[derive(Debug, Clone, PartialEq)] +pub enum CssSize { + Auto, + Px(f32), + Percent(f32), + Vw(f32), + Vh(f32), + /// may be calc or something + Custom(String), +} + +/// extends built-in usize for CSS size +pub trait ConvertRespoCssSize { + fn px(self) -> CssSize; + fn percent(self) -> CssSize; + fn vw(self) -> CssSize; + fn vh(self) -> CssSize; +} + +impl ConvertRespoCssSize for i32 { + fn px(self) -> CssSize { + CssSize::Px(self as f32) + } + fn percent(self) -> CssSize { + CssSize::Percent(self as f32) + } + fn vw(self) -> CssSize { + CssSize::Vw(self as f32) + } + fn vh(self) -> CssSize { + CssSize::Vh(self as f32) + } +} + +impl Display for CssSize { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + match self { + Self::Auto => write!(f, "auto"), + Self::Px(v) => write!(f, "{}px", v), + Self::Percent(v) => write!(f, "{}%", v), + Self::Vw(v) => write!(f, "{}vw", v), + Self::Vh(v) => write!(f, "{}vh", v), + Self::Custom(v) => write!(f, "{}", v), + } + } +} + +/// initially added to support passing both i32 and f32 to methods +pub enum CssPx { + Px(f32), +} + +impl From for CssPx { + fn from(v: f32) -> Self { + Self::Px(v) + } +} + +impl From for CssPx { + fn from(v: i32) -> Self { + Self::Px(v as f32) + } +} + +impl Display for CssPx { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + match self { + Self::Px(v) => write!(f, "{}px", v), + } + } +} diff --git a/respo/src/node/element.rs b/respo/src/node/element.rs index 0be76dc..ed6e492 100644 --- a/respo/src/node/element.rs +++ b/respo/src/node/element.rs @@ -7,6 +7,8 @@ use std::{ use crate::{css::RespoStyle, DispatchFn, RespoEvent, RespoIndexKey, RespoListenerFn, RespoNode}; +use super::css::respo_style; + /// internal abstraction for an element #[derive(Debug, Clone, PartialEq, Eq)] pub struct RespoElement @@ -40,7 +42,7 @@ where name: Rc::from(name), attributes: HashMap::new(), event: HashMap::new(), - style: RespoStyle::default(), + style: respo_style(), children: Vec::new(), } } @@ -51,7 +53,7 @@ where /// attach styles /// ```ignore - /// element.style(RespoStyle::default().margin(10)) + /// element.style(respo_style().margin(10)) /// ``` pub fn style(self, more: RespoStyle) -> Self { let mut style = self.style; diff --git a/respo/src/node/element/alias.rs b/respo/src/node/element/alias.rs index b387d50..148307b 100644 --- a/respo/src/node/element/alias.rs +++ b/respo/src/node/element/alias.rs @@ -4,7 +4,7 @@ use std::fmt::Debug; use super::RespoElement; -use crate::css::{CssSize, RespoStyle}; +use crate::css::{respo_style, ConvertRespoCssSize}; /// a macro for creating a function with a named node /// ```ignore @@ -56,26 +56,22 @@ where { match (w, h) { (Some(wv), Some(hv)) => div().style( - RespoStyle::default() - .width(CssSize::Px(wv as f32)) - .height(CssSize::Px(hv as f32)) - .display(crate::node::css::CssDisplay::InlineBlock), - ), - (Some(wv), None) => span().style( - RespoStyle::default() - .width(CssSize::Px(wv as f32)) + respo_style() + .width(wv.px()) + .height(hv.px()) .display(crate::node::css::CssDisplay::InlineBlock), ), + (Some(wv), None) => span().style(respo_style().width(wv.px()).display(crate::node::css::CssDisplay::InlineBlock)), (None, Some(hv)) => div().style( - RespoStyle::default() - .height(CssSize::Px(hv as f32)) - .width(CssSize::Px(1.0)) + respo_style() + .height(hv.px()) + .width(1.px()) .display(crate::node::css::CssDisplay::Block), ), (None, None) => span().style( - RespoStyle::default() - .width(CssSize::Px(8.)) - .height(CssSize::Px(8.)) + respo_style() + .width(8.px()) + .height(8.px()) .display(crate::node::css::CssDisplay::InlineBlock), ), } diff --git a/respo/src/ui.rs b/respo/src/ui.rs index 3ae8084..d72af62 100644 --- a/respo/src/ui.rs +++ b/respo/src/ui.rs @@ -14,7 +14,7 @@ pub mod dialog; use crate::{ - node::css::{CssBoxSizing, CssColor, CssDisplay, CssLineHeight, CssSize, CssTextAlign, CssVerticalAlign, RespoStyle, *}, + node::css::{CssBoxSizing, CssColor, CssDisplay, CssLineHeight, CssTextAlign, CssVerticalAlign, *}, static_styles, }; @@ -27,31 +27,31 @@ static_styles!( ui_global, ( "&", - RespoStyle::default() + respo_style() .font_family(DEFAULT_FONTS.to_owned()) .line_height(CssLineHeight::Em(2.)) .font_size(14.) .color(CssColor::Hsl(0, 0, 20)), ), - ("& *", RespoStyle::default().box_sizing(CssBoxSizing::BorderBox)) + ("& *", respo_style().box_sizing(CssBoxSizing::BorderBox)) ); static_styles!( ui_input, ( "&", - RespoStyle::default() + respo_style() .border(Some((1., CssBorderStyle::Solid, CssColor::Hsl(0, 0, 80)))) .border_radius(4.) - .padding4(4., 8., 4., 8.) - .min_width(CssSize::Px(120.)) + .padding4(4, 8, 4, 8) + .min_width(120.px()) .line_height(CssLineHeight::Em(1.5)) .font_family(DEFAULT_FONTS.to_owned()) .vertical_align(CssVerticalAlign::Middle), ), ( "&:focus", - RespoStyle::default() + respo_style() .border(Some((1.0, CssBorderStyle::Solid, CssColor::Hsl(200, 50, 75)))) .box_shadow(0.0, 0.0, 4.0, 0.0, CssColor::Hsl(200, 70, 50)), ) @@ -61,24 +61,22 @@ static_styles!( ui_button, ( "&", - RespoStyle::default() + respo_style() .outline(None) .background_color(CssColor::White) .border(Some((1., CssBorderStyle::Solid, CssColor::Hsl(200, 100, 76)))) - .min_width(CssSize::Px(80.)) + .min_width(80.px()) .line_height(CssLineHeight::Px(24.)) .border_radius(4.) .font_size(14.) - .cursor("pointer".to_owned()) + .cursor("pointer") .transition_duration(200.) .text_align(CssTextAlign::Center), ), - ("&:hover", RespoStyle::default().background_color(CssColor::Hsl(0, 0, 98))), + ("&:hover", respo_style().background_color(CssColor::Hsl(0, 0, 98))), ( "&:active", - RespoStyle::default() - .transform(CssTransform::Scale(1.02, 1.02)) - .transition_duration(0.0), + respo_style().transform(CssTransform::Scale(1.02, 1.02)).transition_duration(0.0), ), ); @@ -86,23 +84,23 @@ static_styles!( ui_button_primary, ( "&", - RespoStyle::default() + respo_style() .outline(None) .color(CssColor::White) .background_color(CssColor::Hsl(220, 80, 60)) .border(Some((0., CssBorderStyle::Solid, CssColor::Hsl(220, 80, 60)))) - .min_width(CssSize::Px(80.)) + .min_width(80.px()) .line_height(CssLineHeight::Px(24.)) .border_radius(4.) .font_size(14.) - .cursor("pointer".to_owned()) + .cursor("pointer") .transition_duration(200.) .text_align(CssTextAlign::Center), ), - ("&:hover", RespoStyle::default().background_color(CssColor::Hsl(220, 80, 64))), + ("&:hover", respo_style().background_color(CssColor::Hsl(220, 80, 64))), ( "&:active", - RespoStyle::default() + respo_style() .transform(CssTransform::Scale(1.02, 1.02)) .background_color(CssColor::Hsl(220, 80, 68)) .transition_duration(0.0), @@ -113,23 +111,23 @@ static_styles!( ui_button_danger, ( "&", - RespoStyle::default() + respo_style() .outline(None) .color(CssColor::White) .background_color(CssColor::Hsl(6, 100, 60)) .border(Some((0., CssBorderStyle::Solid, CssColor::Hsl(6, 100, 60)))) - .min_width(CssSize::Px(80.)) + .min_width(80.px()) .line_height(CssLineHeight::Px(24.)) .border_radius(4.) .font_size(14.) - .cursor("pointer".to_owned()) + .cursor("pointer") .transition_duration(200.) .text_align(CssTextAlign::Center), ), - ("&:hover", RespoStyle::default().background_color(CssColor::Hsl(6, 100, 64))), + ("&:hover", respo_style().background_color(CssColor::Hsl(6, 100, 64))), ( "&:active", - RespoStyle::default() + respo_style() .transform(CssTransform::Scale(1.02, 1.02)) .background_color(CssColor::Hsl(6, 100, 68)) .transition_duration(0.0), @@ -140,7 +138,7 @@ static_styles!( ui_center, ( "&", - RespoStyle::default() + respo_style() .display(CssDisplay::Flex) .flex_direction(CssFlexDirection::Column) .justify_content(CssFlexJustifyContent::Center) @@ -152,7 +150,7 @@ static_styles!( column, ( "&", - RespoStyle::default() + respo_style() .display(CssDisplay::Flex) .flex_direction(CssFlexDirection::Column) .align_items(CssFlexAlignItems::Stretch), @@ -163,7 +161,7 @@ static_styles!( ui_column_dispersive, ( "&", - RespoStyle::default() + respo_style() .display(CssDisplay::Flex) .flex_direction(CssFlexDirection::Column) .justify_content(CssFlexJustifyContent::SpaceAround) @@ -175,7 +173,7 @@ static_styles!( ui_column_evenly, ( "&", - RespoStyle::default() + respo_style() .display(CssDisplay::Flex) .flex_direction(CssFlexDirection::Column) .justify_content(CssFlexJustifyContent::SpaceEvenly) @@ -187,7 +185,7 @@ static_styles!( ui_column_parted, ( "&", - RespoStyle::default() + respo_style() .display(CssDisplay::Flex) .flex_direction(CssFlexDirection::Column) .justify_content(CssFlexJustifyContent::SpaceBetween) @@ -195,40 +193,40 @@ static_styles!( ) ); -static_styles!(ui_expand, ("&", RespoStyle::default().insert("flex", "1".to_owned()))); +static_styles!(ui_expand, ("&", respo_style().insert("flex", "1".to_owned()))); static_styles!( ui_fullscreen, ( "&", - RespoStyle::default() + respo_style() .position(CssPosition::Absolute) - .left(CssSize::Px(0.)) - .top(CssSize::Px(0.)) - .width(CssSize::Percent(100.)) - .height(CssSize::Percent(100.)) + .left(0.px()) + .top(0.px()) + .width(100.percent()) + .height(100.percent()) .overflow(CssOverflow::Auto), ) ); -static_styles!(ui_font_code, ("&", RespoStyle::default().font_family(CODE_FONTS.to_owned()))); +static_styles!(ui_font_code, ("&", respo_style().font_family(CODE_FONTS.to_owned()))); -static_styles!(ui_font_fancy, ("&", RespoStyle::default().font_family(FANCY_FONTS.to_owned()))); +static_styles!(ui_font_fancy, ("&", respo_style().font_family(FANCY_FONTS.to_owned()))); -static_styles!(ui_font_normal, ("&", RespoStyle::default().font_family(NORMAL_FONTS.to_owned()))); +static_styles!(ui_font_normal, ("&", respo_style().font_family(NORMAL_FONTS.to_owned()))); static_styles!( ui_textarea, ( "&", - RespoStyle::default() + respo_style() .outline(None) .font_size(14.) .font_family(DEFAULT_FONTS.to_owned()) .border(Some((1., CssBorderStyle::Solid, CssColor::Hsl(0, 0, 20)))) .border_radius(4.) - .padding(8.) - .min_width(CssSize::Px(240.)) + .padding(8) + .min_width(240.px()) .vertical_align(CssVerticalAlign::Top), ) ); @@ -237,15 +235,15 @@ static_styles!( ui_link, ( "&", - RespoStyle::default() + respo_style() .text_decoration(CssTextDecoration::Underline) .insert("user-select", "no-select".to_owned()) - .height(CssSize::Px(24.)) + .height(24.px()) .line_height(CssLineHeight::Px(24.)) - .margin(4.) + .margin(4) .display(CssDisplay::InlineBlock) .color(CssColor::Hsl(200, 100, 76)) - .cursor("pointer".to_owned()), + .cursor("pointer"), ) ); @@ -253,7 +251,7 @@ static_styles!( ui_row, ( "&", - RespoStyle::default() + respo_style() .display(CssDisplay::Flex) .flex_direction(CssFlexDirection::Row) .align_items(CssFlexAlignItems::Stretch), @@ -264,7 +262,7 @@ static_styles!( ui_row_center, ( "&", - RespoStyle::default() + respo_style() .display(CssDisplay::Flex) .flex_direction(CssFlexDirection::Row) .justify_content(CssFlexJustifyContent::Center) @@ -276,7 +274,7 @@ static_styles!( ui_row_dispersive, ( "&", - RespoStyle::default() + respo_style() .display(CssDisplay::Flex) .flex_direction(CssFlexDirection::Row) .justify_content(CssFlexJustifyContent::SpaceAround) @@ -288,7 +286,7 @@ static_styles!( ui_row_evenly, ( "&", - RespoStyle::default() + respo_style() .display(CssDisplay::Flex) .flex_direction(CssFlexDirection::Row) .justify_content(CssFlexJustifyContent::SpaceEvenly) @@ -300,7 +298,7 @@ static_styles!( ui_row_middle, ( "&", - RespoStyle::default() + respo_style() .display(CssDisplay::Flex) .flex_direction(CssFlexDirection::Row) .justify_content(CssFlexJustifyContent::FlexStart) @@ -312,7 +310,7 @@ static_styles!( ui_row_parted, ( "&", - RespoStyle::default() + respo_style() .display(CssDisplay::Flex) .flex_direction(CssFlexDirection::Row) .justify_content(CssFlexJustifyContent::SpaceBetween) diff --git a/respo/src/ui/dialog.rs b/respo/src/ui/dialog.rs index 80fa865..d60cd03 100644 --- a/respo/src/ui/dialog.rs +++ b/respo/src/ui/dialog.rs @@ -14,7 +14,11 @@ use wasm_bindgen::{JsCast, JsValue}; use web_sys::{Element, HtmlElement, KeyboardEvent, KeyboardEventInit, Node}; use crate::component::effect::RespoEffect; -use crate::node::css::{CssColor, CssDisplay, CssOverflow, CssPosition, CssSize, RespoStyle}; +use crate::css::CssColor; +use crate::node::css::{ + CssColor::{Hsl, Hsla}, + CssDisplay, CssOverflow, CssPosition, +}; use crate::node::{DispatchFn, RespoEvent, RespoNode}; use crate::{app, input, static_styles, util, RespoComponent}; @@ -26,6 +30,8 @@ pub use drawer::{DrawerOptions, DrawerPlugin, DrawerPluginInterface, DrawerRende pub use modal::{ModalOptions, ModalPlugin, ModalPluginInterface, ModalRenderer}; pub use prompt::{PromptOptions, PromptPlugin, PromptPluginInterface, PromptValidator}; +use super::{respo_style, ConvertRespoCssSize}; + #[derive(Debug, Clone, PartialEq, Eq)] pub(crate) struct EffectFocus { show: bool, @@ -274,7 +280,7 @@ where RespoComponent::named( "esc-listener", input() - .style(RespoStyle::default().display(CssDisplay::None)) + .style(respo_style().display(CssDisplay::None)) .on_keydown(move |e, dispatch| -> Result<(), String> { if let RespoEvent::Keyboard { key, .. } = e { if key == "Escape" { @@ -294,8 +300,8 @@ static_styles!( css_backdrop, ( "&", - RespoStyle::default() - .background_color(CssColor::Hsla(0.0, 30.0, 10.0, 0.6)) + respo_style() + .background_color(Hsla(0.0, 30.0, 10.0, 0.6)) .position(CssPosition::Fixed) .z_index(999) ) @@ -305,16 +311,16 @@ static_styles!( css_modal_card, ( "&", - RespoStyle::default() - .background_color(CssColor::Hsl(0, 0, 100)) - .max_width(CssSize::Px(600.0)) - .width(CssSize::Percent(100.)) - .max_height(CssSize::Vh(80.0)) + respo_style() + .background_color(Hsl(0, 0, 100)) + .max_width(600.px()) + .width(100.percent()) + .max_height(80.vh()) .overflow(CssOverflow::Auto) .border_radius(3.0) - .color(CssColor::Hsl(0, 0, 0)) + .color(Hsl(0, 0, 0)) .insert("margin", "auto".to_owned()) - .padding(16.0) + .padding(16) ) ); @@ -322,18 +328,18 @@ static_styles!( css_drawer_card, ( "&", - RespoStyle::default() - .background_color(CssColor::Hsl(0, 0, 100)) - .max_width(CssSize::Vw(50.0)) - .width(CssSize::Px(400.)) - .height(CssSize::Vh(100.0)) + respo_style() + .background_color(Hsl(0, 0, 100)) + .max_width(50.vw()) + .width(400.px()) + .height(100.vh()) .overflow(CssOverflow::Auto) - .color(CssColor::Hsl(0, 0, 0)) - .top(CssSize::Px(0.)) - .right(CssSize::Px(0.)) - .bottom(CssSize::Px(0.)) + .color(Hsl(0, 0, 0)) + .top(0.px()) + .right(0.px()) + .bottom(0.px()) .position(CssPosition::Absolute) - .box_shadow(-2., 0., 12., 0., CssColor::Hsla(0., 0., 0., 0.2)) + .box_shadow(-2., 0., 12., 0., Hsla(0., 0., 0., 0.2)) .transform_property("transform, opacity".to_owned()) ) ); @@ -342,9 +348,9 @@ static_styles!( css_button, ( "&", - RespoStyle::default() + respo_style() .border_radius(4.0) .background_color(CssColor::White) - .border_color(CssColor::Hsl(0, 0, 0)) + .border_color(Hsl(0, 0, 0)) ) ); diff --git a/respo/src/ui/dialog/alert.rs b/respo/src/ui/dialog/alert.rs index 739ab88..a5e7eef 100644 --- a/respo/src/ui/dialog/alert.rs +++ b/respo/src/ui/dialog/alert.rs @@ -8,7 +8,7 @@ use serde::{Deserialize, Serialize}; use crate::ui::dialog::{css_backdrop, css_button, css_modal_card}; use crate::ui::dialog::{EffectFocus, EffectModalFade, BUTTON_NAME}; -use crate::ui::{column, ui_button, ui_center, ui_fullscreen, ui_global, ui_row_parted}; +use crate::ui::{column, respo_style, ui_button, ui_center, ui_fullscreen, ui_global, ui_row_parted}; use crate::node::css::{CssLineHeight, CssPosition, RespoStyle}; use crate::node::{DispatchFn, RespoAction, RespoEvent, RespoNode}; @@ -43,60 +43,58 @@ where Ok( RespoComponent::named( "alert-modal", - div() - .style(RespoStyle::default().position(CssPosition::Absolute)) - .elements([if show { - div() - .class_list(&[ui_fullscreen(), ui_center(), css_backdrop()]) - .style(options.backdrop_style) - .on_click({ - let close = close.to_owned(); - move |e, dispatch| -> Result<(), String> { + div().style(respo_style().position(CssPosition::Absolute)).elements([if show { + div() + .class_list(&[ui_fullscreen(), ui_center(), css_backdrop()]) + .style(options.backdrop_style) + .on_click({ + let close = close.to_owned(); + move |e, dispatch| -> Result<(), String> { + if let RespoEvent::Click { original_event, .. } = e { + // stop propagation to prevent closing the modal + original_event.stop_propagation(); + } + close(dispatch)?; + Ok(()) + } + }) + .children([ + div() + .class_list(&[column(), ui_global(), css_modal_card()]) + .style(respo_style().line_height(CssLineHeight::Px(32.0))) + .style(options.card_style) + .on_click(move |e, _dispatch| -> Result<(), String> { + // nothing to do if let RespoEvent::Click { original_event, .. } = e { // stop propagation to prevent closing the modal original_event.stop_propagation(); } - close(dispatch)?; Ok(()) - } - }) - .children([ - div() - .class_list(&[column(), ui_global(), css_modal_card()]) - .style(RespoStyle::default().line_height(CssLineHeight::Px(32.0))) - .style(options.card_style) - .on_click(move |e, _dispatch| -> Result<(), String> { - // nothing to do - if let RespoEvent::Click { original_event, .. } = e { - // stop propagation to prevent closing the modal - original_event.stop_propagation(); - } - Ok(()) - }) - .elements([div().elements([ - span().inner_text(options.text.unwrap_or_else(|| "Alert!".to_owned())), - space(None, Some(8)), - div().class(ui_row_parted()).elements([ - span(), - button() - .class_list(&[ui_button(), css_button(), BUTTON_NAME.to_owned()]) - .inner_text(options.button_text.unwrap_or_else(|| "Read".to_owned())) - .on_click({ - let close = close.to_owned(); - move |_e, dispatch| -> Result<(), String> { - read(dispatch.to_owned())?; - close(dispatch)?; - Ok(()) - } - }), - ]), - ])]) - .to_node(), - comp_esc_listener(show, close)?, - ]) - } else { - span().attr("data-name", "placeholder") - }]), + }) + .elements([div().elements([ + span().inner_text(options.text.unwrap_or_else(|| "Alert!".to_owned())), + space(None, Some(8)), + div().class(ui_row_parted()).elements([ + span(), + button() + .class_list(&[ui_button(), css_button(), BUTTON_NAME.to_owned()]) + .inner_text(options.button_text.unwrap_or_else(|| "Read".to_owned())) + .on_click({ + let close = close.to_owned(); + move |_e, dispatch| -> Result<(), String> { + read(dispatch.to_owned())?; + close(dispatch)?; + Ok(()) + } + }), + ]), + ])]) + .to_node(), + comp_esc_listener(show, close)?, + ]) + } else { + span().attr("data-name", "placeholder") + }]), ) .effect(EffectFocus { show }) .effect(EffectModalFade { show }) diff --git a/respo/src/ui/dialog/confirm.rs b/respo/src/ui/dialog/confirm.rs index 0bfe063..77f404c 100644 --- a/respo/src/ui/dialog/confirm.rs +++ b/respo/src/ui/dialog/confirm.rs @@ -10,7 +10,7 @@ use wasm_bindgen::prelude::Closure; use wasm_bindgen::{JsCast, JsValue}; use crate::ui::dialog::{css_backdrop, css_button, css_modal_card}; -use crate::ui::{column, ui_button, ui_center, ui_fullscreen, ui_global, ui_row_parted}; +use crate::ui::{column, respo_style, ui_button, ui_center, ui_fullscreen, ui_global, ui_row_parted}; use crate::node::css::{CssLineHeight, CssPosition, RespoStyle}; use crate::node::{DispatchFn, RespoAction, RespoEvent, RespoNode}; @@ -49,60 +49,58 @@ where Ok( RespoComponent::named( "confirm-modal", - div() - .style(RespoStyle::default().position(CssPosition::Absolute)) - .elements([if show { - div() - .class_list(&[ui_fullscreen(), ui_center(), css_backdrop()]) - .style(options.backdrop_style) - .on_click({ - let close = close.to_owned(); - move |e, dispatch| -> Result<(), String> { + div().style(respo_style().position(CssPosition::Absolute)).elements([if show { + div() + .class_list(&[ui_fullscreen(), ui_center(), css_backdrop()]) + .style(options.backdrop_style) + .on_click({ + let close = close.to_owned(); + move |e, dispatch| -> Result<(), String> { + if let RespoEvent::Click { original_event, .. } = e { + // stop propagation to prevent closing the modal + original_event.stop_propagation(); + } + close(dispatch)?; + Ok(()) + } + }) + .children([ + div() + .class_list(&[column(), ui_global(), css_modal_card()]) + .style(respo_style().line_height(CssLineHeight::Px(32.0))) + .style(options.card_style) + .on_click(move |e, _dispatch| -> Result<(), String> { + // nothing to do if let RespoEvent::Click { original_event, .. } = e { // stop propagation to prevent closing the modal original_event.stop_propagation(); } - close(dispatch)?; Ok(()) - } - }) - .children([ - div() - .class_list(&[column(), ui_global(), css_modal_card()]) - .style(RespoStyle::default().line_height(CssLineHeight::Px(32.0))) - .style(options.card_style) - .on_click(move |e, _dispatch| -> Result<(), String> { - // nothing to do - if let RespoEvent::Click { original_event, .. } = e { - // stop propagation to prevent closing the modal - original_event.stop_propagation(); - } - Ok(()) - }) - .elements([div().elements([ - span().inner_text(options.text.unwrap_or_else(|| "Need confirmation...".to_owned())), - space(None, Some(8)), - div().class(ui_row_parted()).elements([ - span(), - button() - .class_list(&[ui_button(), css_button(), BUTTON_NAME.to_owned()]) - .inner_text(options.button_text.unwrap_or_else(|| "Confirm".to_owned())) - .on_click({ - let close = close.to_owned(); - move |_e, dispatch| -> Result<(), String> { - confirm(dispatch.to_owned())?; - close(dispatch)?; - Ok(()) - } - }), - ]), - ])]) - .to_node(), - comp_esc_listener(show, close)?, - ]) - } else { - span().attr("data-name", "placeholder") - }]), + }) + .elements([div().elements([ + span().inner_text(options.text.unwrap_or_else(|| "Need confirmation...".to_owned())), + space(None, Some(8)), + div().class(ui_row_parted()).elements([ + span(), + button() + .class_list(&[ui_button(), css_button(), BUTTON_NAME.to_owned()]) + .inner_text(options.button_text.unwrap_or_else(|| "Confirm".to_owned())) + .on_click({ + let close = close.to_owned(); + move |_e, dispatch| -> Result<(), String> { + confirm(dispatch.to_owned())?; + close(dispatch)?; + Ok(()) + } + }), + ]), + ])]) + .to_node(), + comp_esc_listener(show, close)?, + ]) + } else { + span().attr("data-name", "placeholder") + }]), ) .effect(EffectFocus { show }) .effect(EffectModalFade { show }) diff --git a/respo/src/ui/dialog/drawer.rs b/respo/src/ui/dialog/drawer.rs index 6dc3c4d..b6addb1 100644 --- a/respo/src/ui/dialog/drawer.rs +++ b/respo/src/ui/dialog/drawer.rs @@ -7,7 +7,7 @@ use respo_state_derive::RespoState; use serde::{Deserialize, Serialize}; use crate::ui::dialog::{css_backdrop, css_drawer_card}; -use crate::ui::{column, ui_center, ui_fullscreen, ui_global}; +use crate::ui::{column, respo_style, ui_center, ui_fullscreen, ui_global}; use crate::node::css::{CssLineHeight, CssPosition, RespoStyle}; use crate::node::{DispatchFn, RespoAction, RespoEvent, RespoNode}; @@ -90,56 +90,54 @@ where Ok( RespoComponent::named( "drawer", - div() - .style(RespoStyle::default().position(CssPosition::Absolute)) - .elements([if show { - div() - .class_list(&[ui_fullscreen(), ui_center(), css_backdrop()]) - .style(options.backdrop_style) - .on_click({ - let close = close.to_owned(); - move |e, dispatch| -> Result<(), String> { + div().style(respo_style().position(CssPosition::Absolute)).elements([if show { + div() + .class_list(&[ui_fullscreen(), ui_center(), css_backdrop()]) + .style(options.backdrop_style) + .on_click({ + let close = close.to_owned(); + move |e, dispatch| -> Result<(), String> { + if let RespoEvent::Click { original_event, .. } = e { + // stop propagation to prevent closing the drawer + original_event.stop_propagation(); + } + close(dispatch)?; + Ok(()) + } + }) + .children([ + div() + .class_list(&[column(), ui_global(), css_drawer_card()]) + .style(respo_style().padding(0).line_height(CssLineHeight::Px(32.0))) + .style(options.card_style) + .on_click(move |e, _dispatch| -> Result<(), String> { + // nothing to do if let RespoEvent::Click { original_event, .. } = e { // stop propagation to prevent closing the drawer original_event.stop_propagation(); } - close(dispatch)?; Ok(()) - } - }) - .children([ - div() - .class_list(&[column(), ui_global(), css_drawer_card()]) - .style(RespoStyle::default().padding(0.0).line_height(CssLineHeight::Px(32.0))) - .style(options.card_style) - .on_click(move |e, _dispatch| -> Result<(), String> { - // nothing to do - if let RespoEvent::Click { original_event, .. } = e { - // stop propagation to prevent closing the drawer - original_event.stop_propagation(); + }) + .elements([div().class(column()).children([ + div() + .class(ui_center()) + .children([span().inner_text(options.title.unwrap_or_else(|| "Drawer".to_owned())).to_node()]) + .to_node(), + space(None, Some(8)).to_node(), + options.render.run({ + let close = close.to_owned(); + move |dispatch| -> Result<(), String> { + close(dispatch)?; + Ok(()) } - Ok(()) - }) - .elements([div().class(column()).children([ - div() - .class(ui_center()) - .children([span().inner_text(options.title.unwrap_or_else(|| "Drawer".to_owned())).to_node()]) - .to_node(), - space(None, Some(8)).to_node(), - options.render.run({ - let close = close.to_owned(); - move |dispatch| -> Result<(), String> { - close(dispatch)?; - Ok(()) - } - })?, - ])]) - .to_node(), - comp_esc_listener(show, close)?, - ]) - } else { - span().attr("data-name", "placeholder") - }]), + })?, + ])]) + .to_node(), + comp_esc_listener(show, close)?, + ]) + } else { + span().attr("data-name", "placeholder") + }]), ) // .effect(&[show], effect_focus) .effect(EffectDrawerFade { show }) diff --git a/respo/src/ui/dialog/modal.rs b/respo/src/ui/dialog/modal.rs index 375f425..95f74fb 100644 --- a/respo/src/ui/dialog/modal.rs +++ b/respo/src/ui/dialog/modal.rs @@ -7,7 +7,7 @@ use respo_state_derive::RespoState; use serde::{Deserialize, Serialize}; use crate::ui::dialog::{css_backdrop, css_modal_card}; -use crate::ui::{column, ui_center, ui_fullscreen, ui_global}; +use crate::ui::{column, respo_style, ui_center, ui_fullscreen, ui_global}; use crate::node::css::{CssLineHeight, CssPosition, RespoStyle}; use crate::node::{DispatchFn, RespoAction, RespoEvent, RespoNode}; @@ -90,58 +90,56 @@ where Ok( RespoComponent::named( "modal", - div() - .style(RespoStyle::default().position(CssPosition::Absolute)) - .elements([if show { - div() - .class_list(&[ui_fullscreen(), ui_center(), css_backdrop()]) - .style(options.backdrop_style) - .to_owned() - .on_click({ - let close = close.to_owned(); - move |e, dispatch| -> Result<(), String> { + div().style(respo_style().position(CssPosition::Absolute)).elements([if show { + div() + .class_list(&[ui_fullscreen(), ui_center(), css_backdrop()]) + .style(options.backdrop_style) + .to_owned() + .on_click({ + let close = close.to_owned(); + move |e, dispatch| -> Result<(), String> { + if let RespoEvent::Click { original_event, .. } = e { + // stop propagation to prevent closing the modal + original_event.stop_propagation(); + } + close(dispatch)?; + Ok(()) + } + }) + .children([ + div() + .class_list(&[column(), ui_global(), css_modal_card()]) + .style(respo_style().padding(0).line_height(CssLineHeight::Px(32.0))) + .style(options.card_style) + .to_owned() + .on_click(move |e, _dispatch| -> Result<(), String> { + // nothing to do if let RespoEvent::Click { original_event, .. } = e { // stop propagation to prevent closing the modal original_event.stop_propagation(); } - close(dispatch)?; Ok(()) - } - }) - .children([ - div() - .class_list(&[column(), ui_global(), css_modal_card()]) - .style(RespoStyle::default().padding(0.0).line_height(CssLineHeight::Px(32.0))) - .style(options.card_style) - .to_owned() - .on_click(move |e, _dispatch| -> Result<(), String> { - // nothing to do - if let RespoEvent::Click { original_event, .. } = e { - // stop propagation to prevent closing the modal - original_event.stop_propagation(); - } - Ok(()) - }) - .elements([div().class(column()).children([ - div() - .class(ui_center()) - .elements([span().inner_text(options.title.unwrap_or_else(|| "Modal".to_owned()))]) - .to_node(), - space(None, Some(8)).to_node(), - { - let close = close.to_owned(); - options.render.run(move |dispatch| -> Result<(), String> { - close(dispatch)?; - Ok(()) - })? - }, - ])]) - .to_node(), - comp_esc_listener(show, close)?, - ]) - } else { - span().attr("data-name", "placeholder") - }]), + }) + .elements([div().class(column()).children([ + div() + .class(ui_center()) + .elements([span().inner_text(options.title.unwrap_or_else(|| "Modal".to_owned()))]) + .to_node(), + space(None, Some(8)).to_node(), + { + let close = close.to_owned(); + options.render.run(move |dispatch| -> Result<(), String> { + close(dispatch)?; + Ok(()) + })? + }, + ])]) + .to_node(), + comp_esc_listener(show, close)?, + ]) + } else { + span().attr("data-name", "placeholder") + }]), ) // .effect(&[show], effect_focus) .effect(EffectModalFade { show }) diff --git a/respo/src/ui/dialog/prompt.rs b/respo/src/ui/dialog/prompt.rs index 1288521..6e2fd6f 100644 --- a/respo/src/ui/dialog/prompt.rs +++ b/respo/src/ui/dialog/prompt.rs @@ -11,11 +11,11 @@ use wasm_bindgen::prelude::Closure; use wasm_bindgen::{JsCast, JsValue}; use crate::ui::dialog::{css_backdrop, css_button, css_modal_card, EffectModalFade, BUTTON_NAME}; -use crate::ui::{column, ui_button, ui_center, ui_fullscreen, ui_global, ui_input, ui_row_parted, ui_textarea}; +use crate::ui::{column, respo_style, ui_button, ui_center, ui_fullscreen, ui_global, ui_input, ui_row_parted, ui_textarea}; -use crate::node::css::{CssColor, CssLineHeight, CssPosition, CssSize, RespoStyle}; +use crate::node::css::{CssColor, CssLineHeight, CssPosition, RespoStyle}; use crate::node::{DispatchFn, RespoAction, RespoEvent, RespoNode}; -use crate::{app, button, div, input, space, span, static_styles, textarea, util, RespoComponent}; +use crate::{app, button, div, input, space, span, static_styles, textarea, util, ConvertRespoCssSize, RespoComponent}; use crate::states_tree::{RespoState, RespoStatesTree}; @@ -152,73 +152,71 @@ where Ok( RespoComponent::named( "prompt-modal", - div() - .style(RespoStyle::default().position(CssPosition::Absolute)) - .elements([if show { - div() - .class_list(&[ui_fullscreen(), ui_center(), css_backdrop()]) - .style(options.backdrop_style) - .to_owned() - .on_click({ - let close = close.to_owned(); - move |e, dispatch| -> Result<(), String> { + div().style(respo_style().position(CssPosition::Absolute)).elements([if show { + div() + .class_list(&[ui_fullscreen(), ui_center(), css_backdrop()]) + .style(options.backdrop_style) + .to_owned() + .on_click({ + let close = close.to_owned(); + move |e, dispatch| -> Result<(), String> { + if let RespoEvent::Click { original_event, .. } = e { + // stop propagation to prevent closing the modal + original_event.stop_propagation(); + } + { + let dispatch = dispatch.to_owned(); + close(dispatch)?; + } + dispatch.run_empty_state(&cursor)?; + Ok(()) + } + }) + .children([ + div() + .class_list(&[column(), ui_global(), css_modal_card()]) + .style(respo_style().line_height(CssLineHeight::Px(32.0))) + .style(options.card_style) + .style(options.input_style) + .on_click(move |e, _dispatch| -> Result<(), String> { + // nothing to do if let RespoEvent::Click { original_event, .. } = e { // stop propagation to prevent closing the modal original_event.stop_propagation(); } - { - let dispatch = dispatch.to_owned(); - close(dispatch)?; - } - dispatch.run_empty_state(&cursor)?; Ok(()) - } - }) - .children([ - div() - .class_list(&[column(), ui_global(), css_modal_card()]) - .style(RespoStyle::default().line_height(CssLineHeight::Px(32.0))) - .style(options.card_style) - .style(options.input_style) - .on_click(move |e, _dispatch| -> Result<(), String> { - // nothing to do - if let RespoEvent::Click { original_event, .. } = e { - // stop propagation to prevent closing the modal - original_event.stop_propagation(); - } - Ok(()) - }) - .elements([div().elements([ - span().inner_text(options.text.unwrap_or_else(|| "Input your text:".to_owned())), - space(None, Some(8)), - div().elements([input_el - .attrs(&[("value", state.draft.as_str()), ("placeholder", "Content...")]) - .class_list(&[ui_input()]) - .style(RespoStyle::default().width(CssSize::Percent(100.0))) - .value(state.draft.to_owned()) - .on_input(on_text_input)]), - match &state.error { - Some(message) => div().class_list(&[css_error()]).inner_text(message), - None => span(), - }, - space(None, Some(8)), - div().class(ui_row_parted()).elements([ - span(), - button() - .class_list(&[ui_button(), css_button(), BUTTON_NAME.to_owned()]) - .inner_text(options.button_text.unwrap_or_else(|| "Submit".to_owned())) - .on_click(move |_e, dispatch| -> Result<(), String> { - check_submit(&state.draft, dispatch)?; - Ok(()) - }), - ]), - ])]) - .to_node(), - comp_esc_listener(show, close)?, - ]) - } else { - span().attr("data-name", "placeholder") - }]), + }) + .elements([div().elements([ + span().inner_text(options.text.unwrap_or_else(|| "Input your text:".to_owned())), + space(None, Some(8)), + div().elements([input_el + .attrs(&[("value", state.draft.as_str()), ("placeholder", "Content...")]) + .class_list(&[ui_input()]) + .style(respo_style().width(100.percent())) + .value(state.draft.to_owned()) + .on_input(on_text_input)]), + match &state.error { + Some(message) => div().class_list(&[css_error()]).inner_text(message), + None => span(), + }, + space(None, Some(8)), + div().class(ui_row_parted()).elements([ + span(), + button() + .class_list(&[ui_button(), css_button(), BUTTON_NAME.to_owned()]) + .inner_text(options.button_text.unwrap_or_else(|| "Submit".to_owned())) + .on_click(move |_e, dispatch| -> Result<(), String> { + check_submit(&state.draft, dispatch)?; + Ok(()) + }), + ]), + ])]) + .to_node(), + comp_esc_listener(show, close)?, + ]) + } else { + span().attr("data-name", "placeholder") + }]), ) // .effect(&[show], effect_focus) .effect(EffectModalFade { show }) @@ -384,4 +382,4 @@ where } } -static_styles!(css_error, ("&", RespoStyle::default().color(CssColor::Red))); +static_styles!(css_error, ("&", respo_style().color(CssColor::Red)));