From 4832b610b1507719a4c5b51d946e6c42a61e9cc3 Mon Sep 17 00:00:00 2001 From: kostya Date: Wed, 5 Jun 2024 14:05:47 -0700 Subject: [PATCH 01/10] migration to 0.21 --- Cargo.toml | 9 +- examples/basic/Cargo.toml | 8 +- examples/basic/index.html | 2 +- examples/basic/src/main.rs | 104 +++++++++++++++++ src/components/dropdown.rs | 5 +- src/components/modal.rs | 212 +++++++++++++++++++++++++++-------- src/components/navbar.rs | 21 +++- src/components/pagination.rs | 8 +- src/elements/button.rs | 6 +- src/form/input.rs | 2 + src/lib.rs | 9 +- 11 files changed, 327 insertions(+), 59 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index a9f221d..8b29ae2 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -13,11 +13,12 @@ keywords = ["wasm", "web", "bulma", "sass", "yew"] [dependencies] derive_more = { version = "0.99.17", default-features = false, features = ["display"] } web-sys = { version = "0.3.61", features = ["Element", "File", "HtmlCollection", "HtmlSelectElement"] } -yew = { version = "0.20.0", features = ["csr"] } -yew-agent = "0.2.0" -yew-router = { version = "0.17.0", optional = true } -wasm-bindgen = "0.2.84" +yew = { version = "0.21.0", features = ["csr"] } +yew-agent = "0.3.0" +yew-router = { version = "0.18.0", optional = true } +wasm-bindgen = "0.2.87" serde = { version = "1.0.152", features = ["derive"] } +gloo-console = "0.3.0" [features] default = ["router"] diff --git a/examples/basic/Cargo.toml b/examples/basic/Cargo.toml index 1118d5d..e0c9dab 100644 --- a/examples/basic/Cargo.toml +++ b/examples/basic/Cargo.toml @@ -6,10 +6,14 @@ edition = "2018" [dependencies] console_error_panic_hook = "0.1" -gloo-console = "0.2" +gloo-console = "0.3.0" wasm-bindgen = "0.2" ybc = { path = "../../" } -yew = "0.20" +yew = { version = "0.21.0", features = ["csr"] } +yew-agent = "0.3.0" +postcard = "1" +serde = { version = "1", features = ["derive"] } +js-sys = "0.3" [features] default = [] diff --git a/examples/basic/index.html b/examples/basic/index.html index fdc3571..2b9bf08 100644 --- a/examples/basic/index.html +++ b/examples/basic/index.html @@ -4,7 +4,7 @@ Trunk | Yew | YBC - + diff --git a/examples/basic/src/main.rs b/examples/basic/src/main.rs index 3c8e93c..e7ee73c 100644 --- a/examples/basic/src/main.rs +++ b/examples/basic/src/main.rs @@ -1,14 +1,20 @@ #![recursion_limit = "1024"] +use std::rc::Rc; use console_error_panic_hook::set_once as set_panic_hook; use wasm_bindgen::prelude::*; use ybc::TileCtx::{Ancestor, Child, Parent}; use yew::prelude::*; + +use ybc::{ NavBurgerCloserState}; + #[function_component(App)] pub fn app() -> Html { + let state = Rc::new(NavBurgerCloserState { total_clicks: 0 }); html! { <> + > context={state}> Html { }} /> + >> Html { {"YBC"}

{"A Yew component library based on the Bulma CSS framework."}

+ + + + + +
@@ -97,3 +110,94 @@ fn main() { yew::Renderer::::new().render(); } + + +use ybc::ModalCloserProvider; +use ybc::ModalCloserContext; + + +#[function_component] +pub fn MyModal1() -> Html { + let msg_ctx = use_context::().unwrap(); + let onclick = { + Callback::from(move |e:MouseEvent| msg_ctx.dispatch( "id0-close".to_string())) + }; + let on_click_cb = Callback::from(move |e: AttrValue| { + gloo_console::log!("Button clicked!"); + }); + html! { + + {"Open Modal"} + + }} + // on_clicked={on_click_cb} + body={ + html!{ + +

{"This is the body of the modal."}

+
+ } + } + footer={html!( + <> + + {"Save changes"} + + + {"Close"} + + + )} + /> + } +} + +#[function_component] +pub fn MyModal2() -> Html { + let msg_ctx = use_context::().unwrap(); + let onclick = { + Callback::from(move |e:MouseEvent| msg_ctx.dispatch( "id2-close".to_string())) + }; + let msg_ctx2 = use_context::().unwrap(); + let onsave = { + Callback::from(move |e:MouseEvent| msg_ctx2.dispatch( "id2-close".to_string())) + }; + let on_click_cb = Callback::from(move |e: AttrValue| { + gloo_console::log!("Button clicked!"); + }); + html! { + + {"Open Modal"} + + }} + // on_clicked={on_click_cb} + body={ + html!{ + +

{"This is the body of the modal2."}

+
+ } + } + footer={html!( + <> + + {"Save changes"} + + + {"Close"} + + + )} + /> + } +} \ No newline at end of file diff --git a/src/components/dropdown.rs b/src/components/dropdown.rs index fe8ec5a..a56a357 100644 --- a/src/components/dropdown.rs +++ b/src/components/dropdown.rs @@ -61,7 +61,10 @@ impl Component for Dropdown { class.push("is-hoverable"); Callback::noop() } else { - ctx.link().callback(|_| DropdownMsg::Open) + ctx.link().callback(|event: MouseEvent| { + event.prevent_default(); + DropdownMsg::Open + }) }; let overlay = if self.is_menu_active { class.push("is-active"); diff --git a/src/components/modal.rs b/src/components/modal.rs index a86ffbd..11ecbb8 100644 --- a/src/components/modal.rs +++ b/src/components/modal.rs @@ -1,10 +1,12 @@ use std::collections::HashSet; - -use serde::{Deserialize, Serialize}; +use std::rc::Rc; +use wasm_bindgen::JsCast; use yew::prelude::*; -use yew_agent::{use_bridge, HandlerId, Public, UseBridgeHandle, Worker, WorkerLink}; +use yew_agent::worker::{HandlerId, Worker, WorkerScope}; + +use yew_agent::prelude::*; /// Modal actions. pub enum ModalMsg { @@ -13,6 +15,8 @@ pub enum ModalMsg { CloseFromAgent(ModalCloseMsg), } +pub type ModalCloserContext = UseReducerHandle; + #[derive(Clone, Debug, Properties, PartialEq)] pub struct ModalProps { /// The ID of this modal, used for triggering close events from other parts of the app. @@ -36,34 +40,33 @@ pub struct ModalProps { #[function_component(Modal)] pub fn modal(props: &ModalProps) -> Html { let is_active = use_state(|| false); - + let id = props.id.clone(); + let closer_ctx = use_context::().expect("Modal closer in context"); let mut class = Classes::from("modal"); class.push(props.classes.clone()); + let (_id, closed) = match closer_ctx.0.contains("-") { + true => { + let result = closer_ctx.0.split("-").collect::>(); + (result[0], result[1] == "close") + } + false => (closer_ctx.0.as_str(), false), + }; - let (opencb, closecb) = if *is_active { + let (opencb, closecb) = if _id == id && *is_active { class.push("is-active"); let is_active = is_active.clone(); (Callback::noop(), Callback::from(move |_| is_active.set(false))) - } else { + } else if _id == id { let is_active = is_active.clone(); (Callback::from(move |_| is_active.set(true)), Callback::noop()) + } else { + (Callback::noop(), Callback::noop()) }; - { - let id = props.id.clone(); - - let _bridge: UseBridgeHandle = use_bridge(move |response: ModalCloseMsg| { - if response.0 == id { - is_active.set(false); - } else { - } - }); - } - html! { <>
@@ -85,9 +88,9 @@ pub fn modal(props: &ModalProps) -> Html { #[derive(Clone, Debug, Properties, PartialEq)] pub struct ModalCardProps { /// The ID of this modal, used for triggering close events from other parts of the app. - pub id: String, + pub id: AttrValue, /// The title of this modal. - pub title: String, + pub title: AttrValue, /// The content to be placed in the `modal-card-body` not including the modal-card-header / /// modal-card-title, which is handled by the `modal_title` prop. #[prop_or_default] @@ -110,34 +113,130 @@ pub struct ModalCardProps { /// in your app for maximum flexibility. #[function_component(ModalCard)] pub fn modal_card(props: &ModalCardProps) -> Html { + let id = props.id.clone(); + let closer_ctx = use_context::().expect("Modal closer in context"); + + // gloo_console::log!("closer_ctx: full ID {}", closer_ctx.0.as_str()); + + let (_id, closed) = match closer_ctx.0.contains("-") { + true => { + let result = closer_ctx.0.split("-").collect::>(); + (result[0], result[1] == "close") + } + false => (closer_ctx.0.as_str(), false), + }; let is_active = use_state(|| false); + // gloo_console::log!("closer_ctx: {:?} id:{:?} is closed:", _id.clone().into(), id.clone().into(), closed); + + if _id == id && closed { + is_active.set(false); + closer_ctx.dispatch(id.clone()); + // gloo_console::log!("closed!"); + } + let mut class = Classes::from("modal"); class.push(props.classes.clone()); - let (opencb, closecb) = if *is_active { + let (opencb, closecb) = if _id == id && *is_active { + class.push("is-active"); + // gloo_console::log!("is_active=true call"); let is_active = is_active.clone(); - (Callback::noop(), Callback::from(move |_| is_active.set(false))) - } else { + (Callback::noop(), Callback::from(move |e:MouseEvent| { + let target = e.target(); + gloo_console::log!("Close event from modal-card: {:?}"); + // Check if the target is an element that you want to ignore + if let Some(target) = target { + let target_element = target.dyn_into::().unwrap(); + if target_element.id().starts_with("modal-ignore-") { + // If the target is an element to ignore, stop the event propagation + e.stop_propagation(); + gloo_console::log!("Ignoring event"); + return; + } + } + is_active.set(false) + })) + } else if _id == id { let is_active = is_active.clone(); - + gloo_console::log!("is_active=false call"); (Callback::from(move |_| is_active.set(true)), Callback::noop()) + } else { + gloo_console::log!("NOOP call"); + (Callback::noop(), Callback::noop()) + }; + + html! { + <> +
+ {props.trigger.clone()} +
+
+ + + +
+ + } +} + +#[function_component(ModalCard2)] +pub fn modal_card2(props: &ModalCardProps) -> Html { + let id = props.id.clone(); + let closer_ctx = use_context::().expect("Modal closer in context"); + + // gloo_console::log!("closer_ctx: full ID {}", closer_ctx.0.as_str()); + let action = closer_ctx.0.as_str(); + let (_id, closed) = match action.contains("-") { + true => { + let result = action.split("-").collect::>(); + (result[0], result[1] == "close") + } + false => (action, false), }; + let is_active = use_state(|| false); - { - let id = props.id.clone(); + // gloo_console::log!("closer_ctx: {:?} id:{:?} is closed:", _id.clone().into(), id.clone().into(), closed); - let _bridge: UseBridgeHandle = use_bridge(move |response: ModalCloseMsg| { - if response.0 == id { - is_active.set(false); - } else { - } - }); + if _id == id && closed { + is_active.set(false); + closer_ctx.dispatch(id.clone()); + gloo_console::log!("closed!"); } + let mut class = Classes::from("modal"); + class.push(props.classes.clone()); + + let (opencb, closecb) = if _id == id && *is_active { + class.push("is-active"); + gloo_console::log!("is_active=true call"); + + let is_active = is_active.clone(); + + (Callback::noop(), Callback::from(move |_| is_active.set(false))) + } else if _id == id { + let is_active = is_active.clone(); + gloo_console::log!("is_active=false call"); + (Callback::from(move |_| is_active.set(true)), Callback::noop()) + } else { + gloo_console::log!("NOOP call"); + (Callback::noop(), Callback::noop()) + }; + html! { <>
@@ -169,8 +268,16 @@ pub fn modal_card(props: &ModalCardProps) -> Html { /// /// The ID provided in this message must match the ID of the modal which is to be closed, else /// the message will be ignored. -#[derive(Serialize, Deserialize, Clone, Debug)] -pub struct ModalCloseMsg(pub String); +#[derive(Clone, Debug, PartialEq)] +pub struct ModalCloseMsg(pub AttrValue); + +impl Reducible for ModalCloseMsg { + type Action = AttrValue; + + fn reduce(self: Rc, action: Self::Action) -> Rc { + ModalCloseMsg { 0: action }.into() + } +} /// An agent used for being able to close `Modal` & `ModalCard` instances by ID. /// @@ -213,7 +320,7 @@ pub struct ModalCloseMsg(pub String); /// This pattern allows you to communicate with a modal by its given ID, allowing /// you to close the modal from anywhere in your application. pub struct ModalCloser { - link: WorkerLink, + link: WorkerScope, subscribers: HashSet, } @@ -222,27 +329,42 @@ impl Worker for ModalCloser { type Message = (); // The agent receives requests to close modals by ID. type Output = ModalCloseMsg; - type Reach = Public; // The agent forwards the input to all registered modals. - fn create(link: WorkerLink) -> Self { - Self { link, subscribers: HashSet::new() } + fn create(link: &WorkerScope) -> Self { + Self { link: link.clone(), subscribers: HashSet::new() } + } + + fn update(&mut self, scope: &WorkerScope, _: Self::Message) {} + + fn connected(&mut self, scope: &WorkerScope, id: HandlerId) { + self.subscribers.insert(id); } - fn update(&mut self, _: Self::Message) {} + fn disconnected(&mut self, scope: &WorkerScope, id: HandlerId) { + self.subscribers.remove(&id); + } - fn handle_input(&mut self, msg: Self::Input, _: HandlerId) { + fn received(&mut self, scope: &WorkerScope, msg: Self::Input, id: HandlerId) { for cmp in self.subscribers.iter() { self.link.respond(*cmp, msg.clone()); } } +} +#[derive(Properties, Debug, PartialEq)] +pub struct ModalCloserProviderProps { + #[prop_or_default] + pub children: Html, + pub id: String, +} - fn connected(&mut self, id: HandlerId) { - self.subscribers.insert(id); - } - - fn disconnected(&mut self, id: HandlerId) { - self.subscribers.remove(&id); +#[function_component] +pub fn ModalCloserProvider(props: &ModalCloserProviderProps) -> Html { + let msg = use_reducer(|| ModalCloseMsg { 0: props.id.clone().into() }); + html! { + context={ msg }> + { props.children.clone() } + > } } diff --git a/src/components/navbar.rs b/src/components/navbar.rs index 1280583..fd9b850 100644 --- a/src/components/navbar.rs +++ b/src/components/navbar.rs @@ -1,11 +1,19 @@ use derive_more::Display; +use std::rc::Rc; use yew::prelude::*; use crate::components::dropdown::DropdownMsg; +#[derive(Clone, Eq, PartialEq)] +pub struct NavBurgerCloserState { + /// The total number of clicks received. + pub total_clicks: u32, +} + /// The message type used by the `Navbar` component. pub enum NavbarMsg { ToggleMenu, + CloseEvent(Rc), } #[derive(Clone, Debug, Properties, PartialEq)] @@ -56,6 +64,8 @@ pub struct NavbarProps { /// [https://bulma.io/documentation/components/navbar/](https://bulma.io/documentation/components/navbar/) pub struct Navbar { is_menu_open: bool, + _listener: ContextHandle>, + state: Rc, } impl Component for Navbar { @@ -63,7 +73,11 @@ impl Component for Navbar { type Properties = NavbarProps; fn create(_ctx: &Context) -> Self { - Self { is_menu_open: false } + let (state, _listener) = _ctx + .link() + .context::>(_ctx.link().callback(NavbarMsg::CloseEvent)) + .expect("context to be set"); + Self { is_menu_open: false, _listener, state } } fn update(&mut self, _ctx: &Context, msg: Self::Message) -> bool { @@ -71,6 +85,11 @@ impl Component for Navbar { NavbarMsg::ToggleMenu => { self.is_menu_open = !self.is_menu_open; } + NavbarMsg::CloseEvent(state) => { + self.state = state; + // gloo_console::log!("state: {:?}", self.state.total_clicks); + self.is_menu_open = false; + } } true } diff --git a/src/components/pagination.rs b/src/components/pagination.rs index 3145d28..fd27ecf 100644 --- a/src/components/pagination.rs +++ b/src/components/pagination.rs @@ -64,6 +64,8 @@ pub struct PaginationItemProps { /// The click handler for this component. #[prop_or_default] pub onclick: Callback, + #[prop_or_default] + pub current: bool, } /// A pagination element representing a link to a page number, the previous page or the next page. @@ -71,8 +73,12 @@ pub struct PaginationItemProps { /// [https://bulma.io/documentation/components/pagination/](https://bulma.io/documentation/components/pagination/) #[function_component(PaginationItem)] pub fn pagination_item(props: &PaginationItemProps) -> Html { + let effective_class = match props.current { + true => format!("{} is-current", props.item_type.to_string()), + false => format!("{}", props.item_type.to_string()), + }; html! { - + {props.children.clone()} } diff --git a/src/elements/button.rs b/src/elements/button.rs index 426e5f0..daffc42 100644 --- a/src/elements/button.rs +++ b/src/elements/button.rs @@ -61,6 +61,9 @@ pub struct ButtonProps { /// Disable this component. #[prop_or_default] pub disabled: bool, + #[prop_or_default] + pub id: String, + } /// A button element. @@ -74,8 +77,9 @@ pub fn button(props: &ButtonProps) -> Html { props.loading.then_some("is-loading"), props.r#static.then_some("is-static") ); + let _id = props.id.clone(); html! { - } diff --git a/src/form/input.rs b/src/form/input.rs index 17e221b..1dd7450 100644 --- a/src/form/input.rs +++ b/src/form/input.rs @@ -90,4 +90,6 @@ pub enum InputType { Email, #[display(fmt = "tel")] Tel, + #[display(fmt = "number")] + Number, } diff --git a/src/lib.rs b/src/lib.rs index c52614a..330aca6 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -39,10 +39,13 @@ pub use components::card::{ pub use components::dropdown::{Dropdown, DropdownMsg, DropdownProps}; pub use components::menu::{Menu, MenuLabel, MenuLabelProps, MenuList, MenuListProps, MenuProps}; pub use components::message::{Message, MessageBody, MessageBodyProps, MessageHeader, MessageHeaderProps, MessageProps}; -pub use components::modal::{Modal, ModalCard, ModalCardProps, ModalCloseMsg, ModalCloser, ModalMsg, ModalProps}; +pub use components::modal::{ + Modal, ModalCard, ModalCardProps, ModalCloseMsg, ModalCloser, ModalCloserContext, ModalCloserProvider, ModalCloserProviderProps, ModalMsg, + ModalProps, +}; pub use components::navbar::{ - Navbar, NavbarDivider, NavbarDividerProps, NavbarDropdown, NavbarDropdownProps, NavbarFixed, NavbarItem, NavbarItemProps, NavbarItemTag, - NavbarMsg, NavbarProps, + NavBurgerCloserState, Navbar, NavbarDivider, NavbarDividerProps, NavbarDropdown, NavbarDropdownProps, NavbarFixed, NavbarItem, NavbarItemProps, + NavbarItemTag, NavbarMsg, NavbarProps, }; pub use components::pagination::{ Pagination, PaginationEllipsis, PaginationItem, PaginationItemProps, PaginationItemRouter, PaginationItemType, PaginationProps, From 0a7dbd51c3eb9f0ace25516fc3072d081c99075e Mon Sep 17 00:00:00 2001 From: kostya Date: Wed, 5 Jun 2024 14:20:14 -0700 Subject: [PATCH 02/10] migration to 0.21 --- Cargo.toml | 9 +- examples/basic/Cargo.toml | 8 +- examples/basic/index.html | 2 +- examples/basic/src/main.rs | 104 ----------------- src/components/dropdown.rs | 5 +- src/components/modal.rs | 212 ++++++++--------------------------- src/components/navbar.rs | 21 +--- src/components/pagination.rs | 8 +- src/elements/button.rs | 6 +- src/form/input.rs | 2 - src/lib.rs | 9 +- 11 files changed, 59 insertions(+), 327 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 8b29ae2..a9f221d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -13,12 +13,11 @@ keywords = ["wasm", "web", "bulma", "sass", "yew"] [dependencies] derive_more = { version = "0.99.17", default-features = false, features = ["display"] } web-sys = { version = "0.3.61", features = ["Element", "File", "HtmlCollection", "HtmlSelectElement"] } -yew = { version = "0.21.0", features = ["csr"] } -yew-agent = "0.3.0" -yew-router = { version = "0.18.0", optional = true } -wasm-bindgen = "0.2.87" +yew = { version = "0.20.0", features = ["csr"] } +yew-agent = "0.2.0" +yew-router = { version = "0.17.0", optional = true } +wasm-bindgen = "0.2.84" serde = { version = "1.0.152", features = ["derive"] } -gloo-console = "0.3.0" [features] default = ["router"] diff --git a/examples/basic/Cargo.toml b/examples/basic/Cargo.toml index e0c9dab..1118d5d 100644 --- a/examples/basic/Cargo.toml +++ b/examples/basic/Cargo.toml @@ -6,14 +6,10 @@ edition = "2018" [dependencies] console_error_panic_hook = "0.1" -gloo-console = "0.3.0" +gloo-console = "0.2" wasm-bindgen = "0.2" ybc = { path = "../../" } -yew = { version = "0.21.0", features = ["csr"] } -yew-agent = "0.3.0" -postcard = "1" -serde = { version = "1", features = ["derive"] } -js-sys = "0.3" +yew = "0.20" [features] default = [] diff --git a/examples/basic/index.html b/examples/basic/index.html index 2b9bf08..fdc3571 100644 --- a/examples/basic/index.html +++ b/examples/basic/index.html @@ -4,7 +4,7 @@ Trunk | Yew | YBC - + diff --git a/examples/basic/src/main.rs b/examples/basic/src/main.rs index e7ee73c..3c8e93c 100644 --- a/examples/basic/src/main.rs +++ b/examples/basic/src/main.rs @@ -1,20 +1,14 @@ #![recursion_limit = "1024"] -use std::rc::Rc; use console_error_panic_hook::set_once as set_panic_hook; use wasm_bindgen::prelude::*; use ybc::TileCtx::{Ancestor, Child, Parent}; use yew::prelude::*; - -use ybc::{ NavBurgerCloserState}; - #[function_component(App)] pub fn app() -> Html { - let state = Rc::new(NavBurgerCloserState { total_clicks: 0 }); html! { <> - > context={state}> Html { }} /> - >> Html { {"YBC"}

{"A Yew component library based on the Bulma CSS framework."}

- - - - - -
@@ -110,94 +97,3 @@ fn main() { yew::Renderer::::new().render(); } - - -use ybc::ModalCloserProvider; -use ybc::ModalCloserContext; - - -#[function_component] -pub fn MyModal1() -> Html { - let msg_ctx = use_context::().unwrap(); - let onclick = { - Callback::from(move |e:MouseEvent| msg_ctx.dispatch( "id0-close".to_string())) - }; - let on_click_cb = Callback::from(move |e: AttrValue| { - gloo_console::log!("Button clicked!"); - }); - html! { - - {"Open Modal"} - - }} - // on_clicked={on_click_cb} - body={ - html!{ - -

{"This is the body of the modal."}

-
- } - } - footer={html!( - <> - - {"Save changes"} - - - {"Close"} - - - )} - /> - } -} - -#[function_component] -pub fn MyModal2() -> Html { - let msg_ctx = use_context::().unwrap(); - let onclick = { - Callback::from(move |e:MouseEvent| msg_ctx.dispatch( "id2-close".to_string())) - }; - let msg_ctx2 = use_context::().unwrap(); - let onsave = { - Callback::from(move |e:MouseEvent| msg_ctx2.dispatch( "id2-close".to_string())) - }; - let on_click_cb = Callback::from(move |e: AttrValue| { - gloo_console::log!("Button clicked!"); - }); - html! { - - {"Open Modal"} - - }} - // on_clicked={on_click_cb} - body={ - html!{ - -

{"This is the body of the modal2."}

-
- } - } - footer={html!( - <> - - {"Save changes"} - - - {"Close"} - - - )} - /> - } -} \ No newline at end of file diff --git a/src/components/dropdown.rs b/src/components/dropdown.rs index a56a357..fe8ec5a 100644 --- a/src/components/dropdown.rs +++ b/src/components/dropdown.rs @@ -61,10 +61,7 @@ impl Component for Dropdown { class.push("is-hoverable"); Callback::noop() } else { - ctx.link().callback(|event: MouseEvent| { - event.prevent_default(); - DropdownMsg::Open - }) + ctx.link().callback(|_| DropdownMsg::Open) }; let overlay = if self.is_menu_active { class.push("is-active"); diff --git a/src/components/modal.rs b/src/components/modal.rs index 11ecbb8..a86ffbd 100644 --- a/src/components/modal.rs +++ b/src/components/modal.rs @@ -1,12 +1,10 @@ use std::collections::HashSet; -use std::rc::Rc; -use wasm_bindgen::JsCast; -use yew::prelude::*; +use serde::{Deserialize, Serialize}; -use yew_agent::worker::{HandlerId, Worker, WorkerScope}; +use yew::prelude::*; -use yew_agent::prelude::*; +use yew_agent::{use_bridge, HandlerId, Public, UseBridgeHandle, Worker, WorkerLink}; /// Modal actions. pub enum ModalMsg { @@ -15,8 +13,6 @@ pub enum ModalMsg { CloseFromAgent(ModalCloseMsg), } -pub type ModalCloserContext = UseReducerHandle; - #[derive(Clone, Debug, Properties, PartialEq)] pub struct ModalProps { /// The ID of this modal, used for triggering close events from other parts of the app. @@ -40,33 +36,34 @@ pub struct ModalProps { #[function_component(Modal)] pub fn modal(props: &ModalProps) -> Html { let is_active = use_state(|| false); - let id = props.id.clone(); - let closer_ctx = use_context::().expect("Modal closer in context"); + let mut class = Classes::from("modal"); class.push(props.classes.clone()); - let (_id, closed) = match closer_ctx.0.contains("-") { - true => { - let result = closer_ctx.0.split("-").collect::>(); - (result[0], result[1] == "close") - } - false => (closer_ctx.0.as_str(), false), - }; - let (opencb, closecb) = if _id == id && *is_active { + let (opencb, closecb) = if *is_active { class.push("is-active"); let is_active = is_active.clone(); (Callback::noop(), Callback::from(move |_| is_active.set(false))) - } else if _id == id { + } else { let is_active = is_active.clone(); (Callback::from(move |_| is_active.set(true)), Callback::noop()) - } else { - (Callback::noop(), Callback::noop()) }; + { + let id = props.id.clone(); + + let _bridge: UseBridgeHandle = use_bridge(move |response: ModalCloseMsg| { + if response.0 == id { + is_active.set(false); + } else { + } + }); + } + html! { <>
@@ -88,9 +85,9 @@ pub fn modal(props: &ModalProps) -> Html { #[derive(Clone, Debug, Properties, PartialEq)] pub struct ModalCardProps { /// The ID of this modal, used for triggering close events from other parts of the app. - pub id: AttrValue, + pub id: String, /// The title of this modal. - pub title: AttrValue, + pub title: String, /// The content to be placed in the `modal-card-body` not including the modal-card-header / /// modal-card-title, which is handled by the `modal_title` prop. #[prop_or_default] @@ -113,130 +110,34 @@ pub struct ModalCardProps { /// in your app for maximum flexibility. #[function_component(ModalCard)] pub fn modal_card(props: &ModalCardProps) -> Html { - let id = props.id.clone(); - let closer_ctx = use_context::().expect("Modal closer in context"); - - // gloo_console::log!("closer_ctx: full ID {}", closer_ctx.0.as_str()); - - let (_id, closed) = match closer_ctx.0.contains("-") { - true => { - let result = closer_ctx.0.split("-").collect::>(); - (result[0], result[1] == "close") - } - false => (closer_ctx.0.as_str(), false), - }; let is_active = use_state(|| false); - // gloo_console::log!("closer_ctx: {:?} id:{:?} is closed:", _id.clone().into(), id.clone().into(), closed); - - if _id == id && closed { - is_active.set(false); - closer_ctx.dispatch(id.clone()); - // gloo_console::log!("closed!"); - } - let mut class = Classes::from("modal"); class.push(props.classes.clone()); - let (opencb, closecb) = if _id == id && *is_active { - + let (opencb, closecb) = if *is_active { class.push("is-active"); - // gloo_console::log!("is_active=true call"); let is_active = is_active.clone(); - (Callback::noop(), Callback::from(move |e:MouseEvent| { - let target = e.target(); - gloo_console::log!("Close event from modal-card: {:?}"); - // Check if the target is an element that you want to ignore - if let Some(target) = target { - let target_element = target.dyn_into::().unwrap(); - if target_element.id().starts_with("modal-ignore-") { - // If the target is an element to ignore, stop the event propagation - e.stop_propagation(); - gloo_console::log!("Ignoring event"); - return; - } - } - is_active.set(false) - })) - } else if _id == id { - let is_active = is_active.clone(); - gloo_console::log!("is_active=false call"); - (Callback::from(move |_| is_active.set(true)), Callback::noop()) + (Callback::noop(), Callback::from(move |_| is_active.set(false))) } else { - gloo_console::log!("NOOP call"); - (Callback::noop(), Callback::noop()) - }; - - html! { - <> -
- {props.trigger.clone()} -
-
- - - -
- - } -} + let is_active = is_active.clone(); -#[function_component(ModalCard2)] -pub fn modal_card2(props: &ModalCardProps) -> Html { - let id = props.id.clone(); - let closer_ctx = use_context::().expect("Modal closer in context"); - - // gloo_console::log!("closer_ctx: full ID {}", closer_ctx.0.as_str()); - let action = closer_ctx.0.as_str(); - let (_id, closed) = match action.contains("-") { - true => { - let result = action.split("-").collect::>(); - (result[0], result[1] == "close") - } - false => (action, false), + (Callback::from(move |_| is_active.set(true)), Callback::noop()) }; - let is_active = use_state(|| false); - // gloo_console::log!("closer_ctx: {:?} id:{:?} is closed:", _id.clone().into(), id.clone().into(), closed); + { + let id = props.id.clone(); - if _id == id && closed { - is_active.set(false); - closer_ctx.dispatch(id.clone()); - gloo_console::log!("closed!"); + let _bridge: UseBridgeHandle = use_bridge(move |response: ModalCloseMsg| { + if response.0 == id { + is_active.set(false); + } else { + } + }); } - let mut class = Classes::from("modal"); - class.push(props.classes.clone()); - - let (opencb, closecb) = if _id == id && *is_active { - class.push("is-active"); - gloo_console::log!("is_active=true call"); - - let is_active = is_active.clone(); - - (Callback::noop(), Callback::from(move |_| is_active.set(false))) - } else if _id == id { - let is_active = is_active.clone(); - gloo_console::log!("is_active=false call"); - (Callback::from(move |_| is_active.set(true)), Callback::noop()) - } else { - gloo_console::log!("NOOP call"); - (Callback::noop(), Callback::noop()) - }; - html! { <>
@@ -268,16 +169,8 @@ pub fn modal_card2(props: &ModalCardProps) -> Html { /// /// The ID provided in this message must match the ID of the modal which is to be closed, else /// the message will be ignored. -#[derive(Clone, Debug, PartialEq)] -pub struct ModalCloseMsg(pub AttrValue); - -impl Reducible for ModalCloseMsg { - type Action = AttrValue; - - fn reduce(self: Rc, action: Self::Action) -> Rc { - ModalCloseMsg { 0: action }.into() - } -} +#[derive(Serialize, Deserialize, Clone, Debug)] +pub struct ModalCloseMsg(pub String); /// An agent used for being able to close `Modal` & `ModalCard` instances by ID. /// @@ -320,7 +213,7 @@ impl Reducible for ModalCloseMsg { /// This pattern allows you to communicate with a modal by its given ID, allowing /// you to close the modal from anywhere in your application. pub struct ModalCloser { - link: WorkerScope, + link: WorkerLink, subscribers: HashSet, } @@ -329,42 +222,27 @@ impl Worker for ModalCloser { type Message = (); // The agent receives requests to close modals by ID. type Output = ModalCloseMsg; + type Reach = Public; // The agent forwards the input to all registered modals. - fn create(link: &WorkerScope) -> Self { - Self { link: link.clone(), subscribers: HashSet::new() } - } - - fn update(&mut self, scope: &WorkerScope, _: Self::Message) {} - - fn connected(&mut self, scope: &WorkerScope, id: HandlerId) { - self.subscribers.insert(id); + fn create(link: WorkerLink) -> Self { + Self { link, subscribers: HashSet::new() } } - fn disconnected(&mut self, scope: &WorkerScope, id: HandlerId) { - self.subscribers.remove(&id); - } + fn update(&mut self, _: Self::Message) {} - fn received(&mut self, scope: &WorkerScope, msg: Self::Input, id: HandlerId) { + fn handle_input(&mut self, msg: Self::Input, _: HandlerId) { for cmp in self.subscribers.iter() { self.link.respond(*cmp, msg.clone()); } } -} -#[derive(Properties, Debug, PartialEq)] -pub struct ModalCloserProviderProps { - #[prop_or_default] - pub children: Html, - pub id: String, -} -#[function_component] -pub fn ModalCloserProvider(props: &ModalCloserProviderProps) -> Html { - let msg = use_reducer(|| ModalCloseMsg { 0: props.id.clone().into() }); - html! { - context={ msg }> - { props.children.clone() } - > + fn connected(&mut self, id: HandlerId) { + self.subscribers.insert(id); + } + + fn disconnected(&mut self, id: HandlerId) { + self.subscribers.remove(&id); } } diff --git a/src/components/navbar.rs b/src/components/navbar.rs index fd9b850..1280583 100644 --- a/src/components/navbar.rs +++ b/src/components/navbar.rs @@ -1,19 +1,11 @@ use derive_more::Display; -use std::rc::Rc; use yew::prelude::*; use crate::components::dropdown::DropdownMsg; -#[derive(Clone, Eq, PartialEq)] -pub struct NavBurgerCloserState { - /// The total number of clicks received. - pub total_clicks: u32, -} - /// The message type used by the `Navbar` component. pub enum NavbarMsg { ToggleMenu, - CloseEvent(Rc), } #[derive(Clone, Debug, Properties, PartialEq)] @@ -64,8 +56,6 @@ pub struct NavbarProps { /// [https://bulma.io/documentation/components/navbar/](https://bulma.io/documentation/components/navbar/) pub struct Navbar { is_menu_open: bool, - _listener: ContextHandle>, - state: Rc, } impl Component for Navbar { @@ -73,11 +63,7 @@ impl Component for Navbar { type Properties = NavbarProps; fn create(_ctx: &Context) -> Self { - let (state, _listener) = _ctx - .link() - .context::>(_ctx.link().callback(NavbarMsg::CloseEvent)) - .expect("context to be set"); - Self { is_menu_open: false, _listener, state } + Self { is_menu_open: false } } fn update(&mut self, _ctx: &Context, msg: Self::Message) -> bool { @@ -85,11 +71,6 @@ impl Component for Navbar { NavbarMsg::ToggleMenu => { self.is_menu_open = !self.is_menu_open; } - NavbarMsg::CloseEvent(state) => { - self.state = state; - // gloo_console::log!("state: {:?}", self.state.total_clicks); - self.is_menu_open = false; - } } true } diff --git a/src/components/pagination.rs b/src/components/pagination.rs index fd27ecf..3145d28 100644 --- a/src/components/pagination.rs +++ b/src/components/pagination.rs @@ -64,8 +64,6 @@ pub struct PaginationItemProps { /// The click handler for this component. #[prop_or_default] pub onclick: Callback, - #[prop_or_default] - pub current: bool, } /// A pagination element representing a link to a page number, the previous page or the next page. @@ -73,12 +71,8 @@ pub struct PaginationItemProps { /// [https://bulma.io/documentation/components/pagination/](https://bulma.io/documentation/components/pagination/) #[function_component(PaginationItem)] pub fn pagination_item(props: &PaginationItemProps) -> Html { - let effective_class = match props.current { - true => format!("{} is-current", props.item_type.to_string()), - false => format!("{}", props.item_type.to_string()), - }; html! { - + {props.children.clone()} } diff --git a/src/elements/button.rs b/src/elements/button.rs index daffc42..426e5f0 100644 --- a/src/elements/button.rs +++ b/src/elements/button.rs @@ -61,9 +61,6 @@ pub struct ButtonProps { /// Disable this component. #[prop_or_default] pub disabled: bool, - #[prop_or_default] - pub id: String, - } /// A button element. @@ -77,9 +74,8 @@ pub fn button(props: &ButtonProps) -> Html { props.loading.then_some("is-loading"), props.r#static.then_some("is-static") ); - let _id = props.id.clone(); html! { - } diff --git a/src/form/input.rs b/src/form/input.rs index 1dd7450..17e221b 100644 --- a/src/form/input.rs +++ b/src/form/input.rs @@ -90,6 +90,4 @@ pub enum InputType { Email, #[display(fmt = "tel")] Tel, - #[display(fmt = "number")] - Number, } diff --git a/src/lib.rs b/src/lib.rs index 330aca6..c52614a 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -39,13 +39,10 @@ pub use components::card::{ pub use components::dropdown::{Dropdown, DropdownMsg, DropdownProps}; pub use components::menu::{Menu, MenuLabel, MenuLabelProps, MenuList, MenuListProps, MenuProps}; pub use components::message::{Message, MessageBody, MessageBodyProps, MessageHeader, MessageHeaderProps, MessageProps}; -pub use components::modal::{ - Modal, ModalCard, ModalCardProps, ModalCloseMsg, ModalCloser, ModalCloserContext, ModalCloserProvider, ModalCloserProviderProps, ModalMsg, - ModalProps, -}; +pub use components::modal::{Modal, ModalCard, ModalCardProps, ModalCloseMsg, ModalCloser, ModalMsg, ModalProps}; pub use components::navbar::{ - NavBurgerCloserState, Navbar, NavbarDivider, NavbarDividerProps, NavbarDropdown, NavbarDropdownProps, NavbarFixed, NavbarItem, NavbarItemProps, - NavbarItemTag, NavbarMsg, NavbarProps, + Navbar, NavbarDivider, NavbarDividerProps, NavbarDropdown, NavbarDropdownProps, NavbarFixed, NavbarItem, NavbarItemProps, NavbarItemTag, + NavbarMsg, NavbarProps, }; pub use components::pagination::{ Pagination, PaginationEllipsis, PaginationItem, PaginationItemProps, PaginationItemRouter, PaginationItemType, PaginationProps, From 2af7b601007bd1774b1049690170ddca1d3974b3 Mon Sep 17 00:00:00 2001 From: kostya Date: Wed, 5 Jun 2024 14:30:39 -0700 Subject: [PATCH 03/10] migration to 0.21 --- Cargo.toml | 10 +++++----- examples/basic/Cargo.toml | 6 +++--- src/components/modal.rs | 2 +- 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index a9f221d..9c36184 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -2,7 +2,7 @@ name = "ybc" version = "0.4.0" description = "A Yew component library based on the Bulma CSS framework." -authors = ["Anthony Dodd "] +authors = ["Anthony Dodd ", "Konstantin Pupkov "] edition = "2021" license = "MIT/Apache-2.0" repository = "https://github.com/thedodd/ybc" @@ -13,10 +13,10 @@ keywords = ["wasm", "web", "bulma", "sass", "yew"] [dependencies] derive_more = { version = "0.99.17", default-features = false, features = ["display"] } web-sys = { version = "0.3.61", features = ["Element", "File", "HtmlCollection", "HtmlSelectElement"] } -yew = { version = "0.20.0", features = ["csr"] } -yew-agent = "0.2.0" -yew-router = { version = "0.17.0", optional = true } -wasm-bindgen = "0.2.84" +yew = { version = "0.21.0", features = ["csr"] } +yew-agent = "0.3.0" +yew-router = { version = "0.18.0", optional = true } +wasm-bindgen = "0.2" serde = { version = "1.0.152", features = ["derive"] } [features] diff --git a/examples/basic/Cargo.toml b/examples/basic/Cargo.toml index 1118d5d..f0cc65f 100644 --- a/examples/basic/Cargo.toml +++ b/examples/basic/Cargo.toml @@ -6,10 +6,10 @@ edition = "2018" [dependencies] console_error_panic_hook = "0.1" -gloo-console = "0.2" +gloo-console = "0.3.0" wasm-bindgen = "0.2" -ybc = { path = "../../" } -yew = "0.20" +ybc = { path = "../.." } +yew = "0.21" [features] default = [] diff --git a/src/components/modal.rs b/src/components/modal.rs index a86ffbd..d2b2c02 100644 --- a/src/components/modal.rs +++ b/src/components/modal.rs @@ -4,7 +4,7 @@ use serde::{Deserialize, Serialize}; use yew::prelude::*; -use yew_agent::{use_bridge, HandlerId, Public, UseBridgeHandle, Worker, WorkerLink}; +// use yew_agent::{use_bridge, HandlerId, Public, UseBridgeHandle, Worker, WorkerLink}; /// Modal actions. pub enum ModalMsg { From 0587d3e72754b312cec5bd5cbdd8b87d0530d4c6 Mon Sep 17 00:00:00 2001 From: kostya Date: Wed, 5 Jun 2024 14:31:55 -0700 Subject: [PATCH 04/10] migration to 0.21 --- src/components/modal.rs | 212 +++++++++++++++++++++++++++++++--------- 1 file changed, 167 insertions(+), 45 deletions(-) diff --git a/src/components/modal.rs b/src/components/modal.rs index d2b2c02..11ecbb8 100644 --- a/src/components/modal.rs +++ b/src/components/modal.rs @@ -1,10 +1,12 @@ use std::collections::HashSet; - -use serde::{Deserialize, Serialize}; +use std::rc::Rc; +use wasm_bindgen::JsCast; use yew::prelude::*; -// use yew_agent::{use_bridge, HandlerId, Public, UseBridgeHandle, Worker, WorkerLink}; +use yew_agent::worker::{HandlerId, Worker, WorkerScope}; + +use yew_agent::prelude::*; /// Modal actions. pub enum ModalMsg { @@ -13,6 +15,8 @@ pub enum ModalMsg { CloseFromAgent(ModalCloseMsg), } +pub type ModalCloserContext = UseReducerHandle; + #[derive(Clone, Debug, Properties, PartialEq)] pub struct ModalProps { /// The ID of this modal, used for triggering close events from other parts of the app. @@ -36,34 +40,33 @@ pub struct ModalProps { #[function_component(Modal)] pub fn modal(props: &ModalProps) -> Html { let is_active = use_state(|| false); - + let id = props.id.clone(); + let closer_ctx = use_context::().expect("Modal closer in context"); let mut class = Classes::from("modal"); class.push(props.classes.clone()); + let (_id, closed) = match closer_ctx.0.contains("-") { + true => { + let result = closer_ctx.0.split("-").collect::>(); + (result[0], result[1] == "close") + } + false => (closer_ctx.0.as_str(), false), + }; - let (opencb, closecb) = if *is_active { + let (opencb, closecb) = if _id == id && *is_active { class.push("is-active"); let is_active = is_active.clone(); (Callback::noop(), Callback::from(move |_| is_active.set(false))) - } else { + } else if _id == id { let is_active = is_active.clone(); (Callback::from(move |_| is_active.set(true)), Callback::noop()) + } else { + (Callback::noop(), Callback::noop()) }; - { - let id = props.id.clone(); - - let _bridge: UseBridgeHandle = use_bridge(move |response: ModalCloseMsg| { - if response.0 == id { - is_active.set(false); - } else { - } - }); - } - html! { <>
@@ -85,9 +88,9 @@ pub fn modal(props: &ModalProps) -> Html { #[derive(Clone, Debug, Properties, PartialEq)] pub struct ModalCardProps { /// The ID of this modal, used for triggering close events from other parts of the app. - pub id: String, + pub id: AttrValue, /// The title of this modal. - pub title: String, + pub title: AttrValue, /// The content to be placed in the `modal-card-body` not including the modal-card-header / /// modal-card-title, which is handled by the `modal_title` prop. #[prop_or_default] @@ -110,34 +113,130 @@ pub struct ModalCardProps { /// in your app for maximum flexibility. #[function_component(ModalCard)] pub fn modal_card(props: &ModalCardProps) -> Html { + let id = props.id.clone(); + let closer_ctx = use_context::().expect("Modal closer in context"); + + // gloo_console::log!("closer_ctx: full ID {}", closer_ctx.0.as_str()); + + let (_id, closed) = match closer_ctx.0.contains("-") { + true => { + let result = closer_ctx.0.split("-").collect::>(); + (result[0], result[1] == "close") + } + false => (closer_ctx.0.as_str(), false), + }; let is_active = use_state(|| false); + // gloo_console::log!("closer_ctx: {:?} id:{:?} is closed:", _id.clone().into(), id.clone().into(), closed); + + if _id == id && closed { + is_active.set(false); + closer_ctx.dispatch(id.clone()); + // gloo_console::log!("closed!"); + } + let mut class = Classes::from("modal"); class.push(props.classes.clone()); - let (opencb, closecb) = if *is_active { + let (opencb, closecb) = if _id == id && *is_active { + class.push("is-active"); + // gloo_console::log!("is_active=true call"); let is_active = is_active.clone(); - (Callback::noop(), Callback::from(move |_| is_active.set(false))) - } else { + (Callback::noop(), Callback::from(move |e:MouseEvent| { + let target = e.target(); + gloo_console::log!("Close event from modal-card: {:?}"); + // Check if the target is an element that you want to ignore + if let Some(target) = target { + let target_element = target.dyn_into::().unwrap(); + if target_element.id().starts_with("modal-ignore-") { + // If the target is an element to ignore, stop the event propagation + e.stop_propagation(); + gloo_console::log!("Ignoring event"); + return; + } + } + is_active.set(false) + })) + } else if _id == id { let is_active = is_active.clone(); - + gloo_console::log!("is_active=false call"); (Callback::from(move |_| is_active.set(true)), Callback::noop()) + } else { + gloo_console::log!("NOOP call"); + (Callback::noop(), Callback::noop()) + }; + + html! { + <> +
+ {props.trigger.clone()} +
+
+ + + +
+ + } +} + +#[function_component(ModalCard2)] +pub fn modal_card2(props: &ModalCardProps) -> Html { + let id = props.id.clone(); + let closer_ctx = use_context::().expect("Modal closer in context"); + + // gloo_console::log!("closer_ctx: full ID {}", closer_ctx.0.as_str()); + let action = closer_ctx.0.as_str(); + let (_id, closed) = match action.contains("-") { + true => { + let result = action.split("-").collect::>(); + (result[0], result[1] == "close") + } + false => (action, false), }; + let is_active = use_state(|| false); - { - let id = props.id.clone(); + // gloo_console::log!("closer_ctx: {:?} id:{:?} is closed:", _id.clone().into(), id.clone().into(), closed); - let _bridge: UseBridgeHandle = use_bridge(move |response: ModalCloseMsg| { - if response.0 == id { - is_active.set(false); - } else { - } - }); + if _id == id && closed { + is_active.set(false); + closer_ctx.dispatch(id.clone()); + gloo_console::log!("closed!"); } + let mut class = Classes::from("modal"); + class.push(props.classes.clone()); + + let (opencb, closecb) = if _id == id && *is_active { + class.push("is-active"); + gloo_console::log!("is_active=true call"); + + let is_active = is_active.clone(); + + (Callback::noop(), Callback::from(move |_| is_active.set(false))) + } else if _id == id { + let is_active = is_active.clone(); + gloo_console::log!("is_active=false call"); + (Callback::from(move |_| is_active.set(true)), Callback::noop()) + } else { + gloo_console::log!("NOOP call"); + (Callback::noop(), Callback::noop()) + }; + html! { <>
@@ -169,8 +268,16 @@ pub fn modal_card(props: &ModalCardProps) -> Html { /// /// The ID provided in this message must match the ID of the modal which is to be closed, else /// the message will be ignored. -#[derive(Serialize, Deserialize, Clone, Debug)] -pub struct ModalCloseMsg(pub String); +#[derive(Clone, Debug, PartialEq)] +pub struct ModalCloseMsg(pub AttrValue); + +impl Reducible for ModalCloseMsg { + type Action = AttrValue; + + fn reduce(self: Rc, action: Self::Action) -> Rc { + ModalCloseMsg { 0: action }.into() + } +} /// An agent used for being able to close `Modal` & `ModalCard` instances by ID. /// @@ -213,7 +320,7 @@ pub struct ModalCloseMsg(pub String); /// This pattern allows you to communicate with a modal by its given ID, allowing /// you to close the modal from anywhere in your application. pub struct ModalCloser { - link: WorkerLink, + link: WorkerScope, subscribers: HashSet, } @@ -222,27 +329,42 @@ impl Worker for ModalCloser { type Message = (); // The agent receives requests to close modals by ID. type Output = ModalCloseMsg; - type Reach = Public; // The agent forwards the input to all registered modals. - fn create(link: WorkerLink) -> Self { - Self { link, subscribers: HashSet::new() } + fn create(link: &WorkerScope) -> Self { + Self { link: link.clone(), subscribers: HashSet::new() } + } + + fn update(&mut self, scope: &WorkerScope, _: Self::Message) {} + + fn connected(&mut self, scope: &WorkerScope, id: HandlerId) { + self.subscribers.insert(id); } - fn update(&mut self, _: Self::Message) {} + fn disconnected(&mut self, scope: &WorkerScope, id: HandlerId) { + self.subscribers.remove(&id); + } - fn handle_input(&mut self, msg: Self::Input, _: HandlerId) { + fn received(&mut self, scope: &WorkerScope, msg: Self::Input, id: HandlerId) { for cmp in self.subscribers.iter() { self.link.respond(*cmp, msg.clone()); } } +} +#[derive(Properties, Debug, PartialEq)] +pub struct ModalCloserProviderProps { + #[prop_or_default] + pub children: Html, + pub id: String, +} - fn connected(&mut self, id: HandlerId) { - self.subscribers.insert(id); - } - - fn disconnected(&mut self, id: HandlerId) { - self.subscribers.remove(&id); +#[function_component] +pub fn ModalCloserProvider(props: &ModalCloserProviderProps) -> Html { + let msg = use_reducer(|| ModalCloseMsg { 0: props.id.clone().into() }); + html! { + context={ msg }> + { props.children.clone() } + > } } From 8383158fff94a18cdd389f9a13da224c554c0af0 Mon Sep 17 00:00:00 2001 From: kostya Date: Wed, 5 Jun 2024 14:39:31 -0700 Subject: [PATCH 05/10] migration to 0.21 --- Cargo.toml | 1 + examples/basic/Cargo.toml | 2 +- examples/basic/index.html | 2 +- examples/basic/src/main.rs | 104 +++++++++++++++++++++++++++++++++++ src/components/dropdown.rs | 5 +- src/components/navbar.rs | 21 ++++++- src/components/pagination.rs | 8 ++- src/elements/button.rs | 6 +- src/form/input.rs | 2 + src/lib.rs | 9 ++- 10 files changed, 151 insertions(+), 9 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 9c36184..aaf0d47 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -18,6 +18,7 @@ yew-agent = "0.3.0" yew-router = { version = "0.18.0", optional = true } wasm-bindgen = "0.2" serde = { version = "1.0.152", features = ["derive"] } +gloo-console = "0.3.0" [features] default = ["router"] diff --git a/examples/basic/Cargo.toml b/examples/basic/Cargo.toml index f0cc65f..47f139c 100644 --- a/examples/basic/Cargo.toml +++ b/examples/basic/Cargo.toml @@ -6,7 +6,7 @@ edition = "2018" [dependencies] console_error_panic_hook = "0.1" -gloo-console = "0.3.0" +gloo-console = "0.3" wasm-bindgen = "0.2" ybc = { path = "../.." } yew = "0.21" diff --git a/examples/basic/index.html b/examples/basic/index.html index fdc3571..2b9bf08 100644 --- a/examples/basic/index.html +++ b/examples/basic/index.html @@ -4,7 +4,7 @@ Trunk | Yew | YBC - + diff --git a/examples/basic/src/main.rs b/examples/basic/src/main.rs index 3c8e93c..e7ee73c 100644 --- a/examples/basic/src/main.rs +++ b/examples/basic/src/main.rs @@ -1,14 +1,20 @@ #![recursion_limit = "1024"] +use std::rc::Rc; use console_error_panic_hook::set_once as set_panic_hook; use wasm_bindgen::prelude::*; use ybc::TileCtx::{Ancestor, Child, Parent}; use yew::prelude::*; + +use ybc::{ NavBurgerCloserState}; + #[function_component(App)] pub fn app() -> Html { + let state = Rc::new(NavBurgerCloserState { total_clicks: 0 }); html! { <> + > context={state}> Html { }} /> + >> Html { {"YBC"}

{"A Yew component library based on the Bulma CSS framework."}

+ + + + + +
@@ -97,3 +110,94 @@ fn main() { yew::Renderer::::new().render(); } + + +use ybc::ModalCloserProvider; +use ybc::ModalCloserContext; + + +#[function_component] +pub fn MyModal1() -> Html { + let msg_ctx = use_context::().unwrap(); + let onclick = { + Callback::from(move |e:MouseEvent| msg_ctx.dispatch( "id0-close".to_string())) + }; + let on_click_cb = Callback::from(move |e: AttrValue| { + gloo_console::log!("Button clicked!"); + }); + html! { + + {"Open Modal"} + + }} + // on_clicked={on_click_cb} + body={ + html!{ + +

{"This is the body of the modal."}

+
+ } + } + footer={html!( + <> + + {"Save changes"} + + + {"Close"} + + + )} + /> + } +} + +#[function_component] +pub fn MyModal2() -> Html { + let msg_ctx = use_context::().unwrap(); + let onclick = { + Callback::from(move |e:MouseEvent| msg_ctx.dispatch( "id2-close".to_string())) + }; + let msg_ctx2 = use_context::().unwrap(); + let onsave = { + Callback::from(move |e:MouseEvent| msg_ctx2.dispatch( "id2-close".to_string())) + }; + let on_click_cb = Callback::from(move |e: AttrValue| { + gloo_console::log!("Button clicked!"); + }); + html! { + + {"Open Modal"} + + }} + // on_clicked={on_click_cb} + body={ + html!{ + +

{"This is the body of the modal2."}

+
+ } + } + footer={html!( + <> + + {"Save changes"} + + + {"Close"} + + + )} + /> + } +} \ No newline at end of file diff --git a/src/components/dropdown.rs b/src/components/dropdown.rs index fe8ec5a..a56a357 100644 --- a/src/components/dropdown.rs +++ b/src/components/dropdown.rs @@ -61,7 +61,10 @@ impl Component for Dropdown { class.push("is-hoverable"); Callback::noop() } else { - ctx.link().callback(|_| DropdownMsg::Open) + ctx.link().callback(|event: MouseEvent| { + event.prevent_default(); + DropdownMsg::Open + }) }; let overlay = if self.is_menu_active { class.push("is-active"); diff --git a/src/components/navbar.rs b/src/components/navbar.rs index 1280583..fd9b850 100644 --- a/src/components/navbar.rs +++ b/src/components/navbar.rs @@ -1,11 +1,19 @@ use derive_more::Display; +use std::rc::Rc; use yew::prelude::*; use crate::components::dropdown::DropdownMsg; +#[derive(Clone, Eq, PartialEq)] +pub struct NavBurgerCloserState { + /// The total number of clicks received. + pub total_clicks: u32, +} + /// The message type used by the `Navbar` component. pub enum NavbarMsg { ToggleMenu, + CloseEvent(Rc), } #[derive(Clone, Debug, Properties, PartialEq)] @@ -56,6 +64,8 @@ pub struct NavbarProps { /// [https://bulma.io/documentation/components/navbar/](https://bulma.io/documentation/components/navbar/) pub struct Navbar { is_menu_open: bool, + _listener: ContextHandle>, + state: Rc, } impl Component for Navbar { @@ -63,7 +73,11 @@ impl Component for Navbar { type Properties = NavbarProps; fn create(_ctx: &Context) -> Self { - Self { is_menu_open: false } + let (state, _listener) = _ctx + .link() + .context::>(_ctx.link().callback(NavbarMsg::CloseEvent)) + .expect("context to be set"); + Self { is_menu_open: false, _listener, state } } fn update(&mut self, _ctx: &Context, msg: Self::Message) -> bool { @@ -71,6 +85,11 @@ impl Component for Navbar { NavbarMsg::ToggleMenu => { self.is_menu_open = !self.is_menu_open; } + NavbarMsg::CloseEvent(state) => { + self.state = state; + // gloo_console::log!("state: {:?}", self.state.total_clicks); + self.is_menu_open = false; + } } true } diff --git a/src/components/pagination.rs b/src/components/pagination.rs index 3145d28..fd27ecf 100644 --- a/src/components/pagination.rs +++ b/src/components/pagination.rs @@ -64,6 +64,8 @@ pub struct PaginationItemProps { /// The click handler for this component. #[prop_or_default] pub onclick: Callback, + #[prop_or_default] + pub current: bool, } /// A pagination element representing a link to a page number, the previous page or the next page. @@ -71,8 +73,12 @@ pub struct PaginationItemProps { /// [https://bulma.io/documentation/components/pagination/](https://bulma.io/documentation/components/pagination/) #[function_component(PaginationItem)] pub fn pagination_item(props: &PaginationItemProps) -> Html { + let effective_class = match props.current { + true => format!("{} is-current", props.item_type.to_string()), + false => format!("{}", props.item_type.to_string()), + }; html! { - + {props.children.clone()} } diff --git a/src/elements/button.rs b/src/elements/button.rs index 426e5f0..daffc42 100644 --- a/src/elements/button.rs +++ b/src/elements/button.rs @@ -61,6 +61,9 @@ pub struct ButtonProps { /// Disable this component. #[prop_or_default] pub disabled: bool, + #[prop_or_default] + pub id: String, + } /// A button element. @@ -74,8 +77,9 @@ pub fn button(props: &ButtonProps) -> Html { props.loading.then_some("is-loading"), props.r#static.then_some("is-static") ); + let _id = props.id.clone(); html! { - } diff --git a/src/form/input.rs b/src/form/input.rs index 17e221b..1dd7450 100644 --- a/src/form/input.rs +++ b/src/form/input.rs @@ -90,4 +90,6 @@ pub enum InputType { Email, #[display(fmt = "tel")] Tel, + #[display(fmt = "number")] + Number, } diff --git a/src/lib.rs b/src/lib.rs index c52614a..330aca6 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -39,10 +39,13 @@ pub use components::card::{ pub use components::dropdown::{Dropdown, DropdownMsg, DropdownProps}; pub use components::menu::{Menu, MenuLabel, MenuLabelProps, MenuList, MenuListProps, MenuProps}; pub use components::message::{Message, MessageBody, MessageBodyProps, MessageHeader, MessageHeaderProps, MessageProps}; -pub use components::modal::{Modal, ModalCard, ModalCardProps, ModalCloseMsg, ModalCloser, ModalMsg, ModalProps}; +pub use components::modal::{ + Modal, ModalCard, ModalCardProps, ModalCloseMsg, ModalCloser, ModalCloserContext, ModalCloserProvider, ModalCloserProviderProps, ModalMsg, + ModalProps, +}; pub use components::navbar::{ - Navbar, NavbarDivider, NavbarDividerProps, NavbarDropdown, NavbarDropdownProps, NavbarFixed, NavbarItem, NavbarItemProps, NavbarItemTag, - NavbarMsg, NavbarProps, + NavBurgerCloserState, Navbar, NavbarDivider, NavbarDividerProps, NavbarDropdown, NavbarDropdownProps, NavbarFixed, NavbarItem, NavbarItemProps, + NavbarItemTag, NavbarMsg, NavbarProps, }; pub use components::pagination::{ Pagination, PaginationEllipsis, PaginationItem, PaginationItemProps, PaginationItemRouter, PaginationItemType, PaginationProps, From 14e16a39b2ac3e09d3197cba1524f659e7cd1e1a Mon Sep 17 00:00:00 2001 From: kostya Date: Wed, 5 Jun 2024 14:43:58 -0700 Subject: [PATCH 06/10] migration to 0.21 --- README.md | 2 +- examples/basic/src/main.rs | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 03e0442..e5c8ee2 100644 --- a/README.md +++ b/README.md @@ -61,7 +61,7 @@ ybc = "*" ### add bulma #### add bulma css (no customizations) -This project works perfectly well if you just include the Bulma CSS in your HTML, [as described here](https://bulma.io/documentation/overview/start/). The following link in your HTML head should do the trick: ``. +This project works perfectly well if you just include the Bulma CSS in your HTML, [as described here](https://bulma.io/documentation/overview/start/). The following link in your HTML head should do the trick: ``. #### add bulma sass (allows customization & themes) However, if you want to customize Bulma to match your style guidelines, then you will need to have a copy of the Bulma SASS locally, and then import Bulma after you've defined your customizations, [as described here](https://bulma.io/documentation/customize/). diff --git a/examples/basic/src/main.rs b/examples/basic/src/main.rs index e7ee73c..0154562 100644 --- a/examples/basic/src/main.rs +++ b/examples/basic/src/main.rs @@ -37,7 +37,7 @@ pub fn app() -> Html { - + {"YBC"} @@ -120,7 +120,7 @@ use ybc::ModalCloserContext; pub fn MyModal1() -> Html { let msg_ctx = use_context::().unwrap(); let onclick = { - Callback::from(move |e:MouseEvent| msg_ctx.dispatch( "id0-close".to_string())) + Callback::from(move |e:MouseEvent| msg_ctx.dispatch( "id0-close".to_string().parse().unwrap())) }; let on_click_cb = Callback::from(move |e: AttrValue| { gloo_console::log!("Button clicked!"); @@ -161,11 +161,11 @@ pub fn MyModal1() -> Html { pub fn MyModal2() -> Html { let msg_ctx = use_context::().unwrap(); let onclick = { - Callback::from(move |e:MouseEvent| msg_ctx.dispatch( "id2-close".to_string())) + Callback::from(move |e:MouseEvent| msg_ctx.dispatch( "id2-close".to_string().parse().unwrap())) }; let msg_ctx2 = use_context::().unwrap(); let onsave = { - Callback::from(move |e:MouseEvent| msg_ctx2.dispatch( "id2-close".to_string())) + Callback::from(move |e:MouseEvent| msg_ctx2.dispatch( "id2-close".to_string().parse().unwrap())) }; let on_click_cb = Callback::from(move |e: AttrValue| { gloo_console::log!("Button clicked!"); From 131b3fa31f5df02f7436c950e2a1f51aa87a3013 Mon Sep 17 00:00:00 2001 From: kostya Date: Wed, 5 Jun 2024 14:49:05 -0700 Subject: [PATCH 07/10] remove debug comments --- src/components/modal.rs | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/components/modal.rs b/src/components/modal.rs index 11ecbb8..b0808eb 100644 --- a/src/components/modal.rs +++ b/src/components/modal.rs @@ -147,14 +147,14 @@ pub fn modal_card(props: &ModalCardProps) -> Html { (Callback::noop(), Callback::from(move |e:MouseEvent| { let target = e.target(); - gloo_console::log!("Close event from modal-card: {:?}"); + // gloo_console::log!("Close event from modal-card: {:?}"); // Check if the target is an element that you want to ignore if let Some(target) = target { let target_element = target.dyn_into::().unwrap(); if target_element.id().starts_with("modal-ignore-") { // If the target is an element to ignore, stop the event propagation e.stop_propagation(); - gloo_console::log!("Ignoring event"); + // gloo_console::log!("Ignoring event"); return; } } @@ -162,10 +162,10 @@ pub fn modal_card(props: &ModalCardProps) -> Html { })) } else if _id == id { let is_active = is_active.clone(); - gloo_console::log!("is_active=false call"); + // gloo_console::log!("is_active=false call"); (Callback::from(move |_| is_active.set(true)), Callback::noop()) } else { - gloo_console::log!("NOOP call"); + // gloo_console::log!("NOOP call"); (Callback::noop(), Callback::noop()) }; @@ -215,7 +215,7 @@ pub fn modal_card2(props: &ModalCardProps) -> Html { if _id == id && closed { is_active.set(false); closer_ctx.dispatch(id.clone()); - gloo_console::log!("closed!"); + // gloo_console::log!("closed!"); } let mut class = Classes::from("modal"); @@ -223,17 +223,17 @@ pub fn modal_card2(props: &ModalCardProps) -> Html { let (opencb, closecb) = if _id == id && *is_active { class.push("is-active"); - gloo_console::log!("is_active=true call"); + // gloo_console::log!("is_active=true call"); let is_active = is_active.clone(); (Callback::noop(), Callback::from(move |_| is_active.set(false))) } else if _id == id { let is_active = is_active.clone(); - gloo_console::log!("is_active=false call"); + // gloo_console::log!("is_active=false call"); (Callback::from(move |_| is_active.set(true)), Callback::noop()) } else { - gloo_console::log!("NOOP call"); + // gloo_console::log!("NOOP call"); (Callback::noop(), Callback::noop()) }; From 21236f94a0702bc0c9c2bf983fe11fdcffd4eb6d Mon Sep 17 00:00:00 2001 From: kostya Date: Wed, 5 Jun 2024 14:49:51 -0700 Subject: [PATCH 08/10] remove debug comments --- src/components/modal.rs | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/src/components/modal.rs b/src/components/modal.rs index b0808eb..1c5a6ce 100644 --- a/src/components/modal.rs +++ b/src/components/modal.rs @@ -286,13 +286,7 @@ impl Reducible for ModalCloseMsg { /// /// First, in your component which is using this modal, configure a `ModalCloser` dispatcher. /// ```rust -/// use yew::agent::Dispatcher; -/// use yew::prelude::*; -/// // .. snip .. -/// fn create(props: Self::Properties, link: ComponentLink) -> Self { -/// let bridge = ModalCloser::dispatcher(); -/// Self { link, props, bridge } -/// } + /// ``` /// /// Next, in your component's `view` method, setup a callback to handle your component's close From a14f0e810f017214cc06f6937009b54e1675ba53 Mon Sep 17 00:00:00 2001 From: kostya Date: Wed, 5 Jun 2024 15:08:40 -0700 Subject: [PATCH 09/10] remove debug comments --- src/components/modal.rs | 23 ----------------------- 1 file changed, 23 deletions(-) diff --git a/src/components/modal.rs b/src/components/modal.rs index 1c5a6ce..a6c3b01 100644 --- a/src/components/modal.rs +++ b/src/components/modal.rs @@ -116,8 +116,6 @@ pub fn modal_card(props: &ModalCardProps) -> Html { let id = props.id.clone(); let closer_ctx = use_context::().expect("Modal closer in context"); - // gloo_console::log!("closer_ctx: full ID {}", closer_ctx.0.as_str()); - let (_id, closed) = match closer_ctx.0.contains("-") { true => { let result = closer_ctx.0.split("-").collect::>(); @@ -127,12 +125,9 @@ pub fn modal_card(props: &ModalCardProps) -> Html { }; let is_active = use_state(|| false); - // gloo_console::log!("closer_ctx: {:?} id:{:?} is closed:", _id.clone().into(), id.clone().into(), closed); - if _id == id && closed { is_active.set(false); closer_ctx.dispatch(id.clone()); - // gloo_console::log!("closed!"); } let mut class = Classes::from("modal"); @@ -141,20 +136,16 @@ pub fn modal_card(props: &ModalCardProps) -> Html { let (opencb, closecb) = if _id == id && *is_active { class.push("is-active"); - // gloo_console::log!("is_active=true call"); let is_active = is_active.clone(); (Callback::noop(), Callback::from(move |e:MouseEvent| { let target = e.target(); - // gloo_console::log!("Close event from modal-card: {:?}"); - // Check if the target is an element that you want to ignore if let Some(target) = target { let target_element = target.dyn_into::().unwrap(); if target_element.id().starts_with("modal-ignore-") { // If the target is an element to ignore, stop the event propagation e.stop_propagation(); - // gloo_console::log!("Ignoring event"); return; } } @@ -165,7 +156,6 @@ pub fn modal_card(props: &ModalCardProps) -> Html { // gloo_console::log!("is_active=false call"); (Callback::from(move |_| is_active.set(true)), Callback::noop()) } else { - // gloo_console::log!("NOOP call"); (Callback::noop(), Callback::noop()) }; @@ -199,7 +189,6 @@ pub fn modal_card2(props: &ModalCardProps) -> Html { let id = props.id.clone(); let closer_ctx = use_context::().expect("Modal closer in context"); - // gloo_console::log!("closer_ctx: full ID {}", closer_ctx.0.as_str()); let action = closer_ctx.0.as_str(); let (_id, closed) = match action.contains("-") { true => { @@ -210,12 +199,10 @@ pub fn modal_card2(props: &ModalCardProps) -> Html { }; let is_active = use_state(|| false); - // gloo_console::log!("closer_ctx: {:?} id:{:?} is closed:", _id.clone().into(), id.clone().into(), closed); if _id == id && closed { is_active.set(false); closer_ctx.dispatch(id.clone()); - // gloo_console::log!("closed!"); } let mut class = Classes::from("modal"); @@ -230,10 +217,8 @@ pub fn modal_card2(props: &ModalCardProps) -> Html { (Callback::noop(), Callback::from(move |_| is_active.set(false))) } else if _id == id { let is_active = is_active.clone(); - // gloo_console::log!("is_active=false call"); (Callback::from(move |_| is_active.set(true)), Callback::noop()) } else { - // gloo_console::log!("NOOP call"); (Callback::noop(), Callback::noop()) }; @@ -302,14 +287,6 @@ impl Reducible for ModalCloseMsg { /// /> /// ``` /// -/// Finally, in your component's `update` method, send the `ModalCloseMsg` over to the agent which -/// will forward the message to the modal to cause it to close. -/// ```rust -/// fn update(&mut self, msg: Self::Message) -> ShouldRender { -/// self.bridge.send(msg); -/// true -/// } -/// ``` /// /// This pattern allows you to communicate with a modal by its given ID, allowing /// you to close the modal from anywhere in your application. From d3c2c8ee5b31d5d73dae992c7b2acfd8c0179207 Mon Sep 17 00:00:00 2001 From: kostya Date: Wed, 5 Jun 2024 15:09:23 -0700 Subject: [PATCH 10/10] remove debug comments --- src/components/navbar.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/components/navbar.rs b/src/components/navbar.rs index fd9b850..e8c5796 100644 --- a/src/components/navbar.rs +++ b/src/components/navbar.rs @@ -87,7 +87,6 @@ impl Component for Navbar { } NavbarMsg::CloseEvent(state) => { self.state = state; - // gloo_console::log!("state: {:?}", self.state.total_clicks); self.is_menu_open = false; } }