seed/
lib.rs

1//! Visit the [website](https://seed-rs.org/)
2//!
3//! See the [github Readme](https://github.com/seed-rs/seed) for details
4//!
5//!
6//! ## Counter Example
7//! ```
8//! use seed::{prelude::*, *};
9//!
10//! // `init` describes what should happen when your app started.
11//! fn init(_: Url, _: &mut impl Orders<Msg>) -> Model {
12//!     Model { counter: 0 }
13//! }
14//!
15//! // `Model` describes our app state.
16//! struct Model { counter: i32 }
17//!
18//! // `Msg` describes the different events you can modify state with.
19//! enum Msg {
20//!     Increment,
21//! }
22//!
23//! // `update` describes how to handle each `Msg`.
24//! fn update(msg: Msg, model: &mut Model, _: &mut impl Orders<Msg>) {
25//!     match msg {
26//!         Msg::Increment => model.counter += 1,
27//!     }
28//! }
29//!
30//! // `view` describes what to display.
31//! fn view(model: &Model) -> Node<Msg> {
32//!     div![
33//!         "This is a counter: ",
34//!         C!["counter"],
35//!         button![
36//!             model.counter,
37//!             ev(Ev::Click, |_| Msg::Increment),
38//!         ],
39//!     ]
40//! }
41//!
42//! #[wasm_bindgen(start)]
43//! pub fn start() {
44//!     // Mount the `app` to the element with the `id` "app".
45//!     App::start("app", init, update, view);
46//! }
47//! ```
48
49//#![deny(missing_docs)]
50#![forbid(unsafe_code)]
51#![allow(
52    clippy::use_self,
53    clippy::single_match_else,
54    clippy::must_use_candidate,
55    clippy::wildcard_imports,
56    clippy::used_underscore_binding,
57    clippy::future_not_send
58)]
59#![allow(deprecated)]
60
61// @TODO Refactor once `optin_builtin_traits` or `negative_impls`
62// @TODO is stable (https://github.com/seed-rs/seed/issues/391).
63// --
64// @TODO Remove `'static` bound from all `MsU`s once `optin_builtin_traits`, `negative_impls`
65// @TODO or https://github.com/rust-lang/rust/issues/41875 is stable.
66macro_rules! map_callback_return_to_option_ms {
67    ($cb_type:ty, $callback:expr, $panic_text:literal, $output_type:tt) => {{
68        let t_type = std::any::TypeId::of::<MsU>();
69        if t_type == std::any::TypeId::of::<Ms>() {
70            $output_type::new(move |value| {
71                (&mut Some($callback(value)) as &mut dyn std::any::Any)
72                    .downcast_mut::<Option<Ms>>()
73                    .and_then(Option::take)
74            })
75        } else if t_type == std::any::TypeId::of::<Option<Ms>>() {
76            $output_type::new(move |value| {
77                (&mut $callback(value) as &mut dyn std::any::Any)
78                    .downcast_mut::<Option<Ms>>()
79                    .and_then(Option::take)
80            })
81        } else if t_type == std::any::TypeId::of::<()>() {
82            $output_type::new(move |value| {
83                $callback(value);
84                None
85            }) as $output_type<$cb_type>
86        } else {
87            panic!($panic_text);
88        }
89    }};
90}
91// @TODO move to prelude (?)
92pub use crate::{
93    app::App,
94    browser::dom::cast::{
95        to_drag_event, to_html_el, to_input, to_keyboard_event, to_mouse_event, to_select,
96        to_textarea, to_touch_event, to_wheel_event,
97    },
98    browser::url::Url,
99    browser::util::{
100        self, body, canvas, canvas_context_2d, document, error, history, html_document, log, window,
101    },
102    virtual_dom::{Attrs, EventHandler, Style},
103};
104
105pub use futures::{
106    self,
107    future::{self, FutureExt, TryFutureExt},
108};
109use wasm_bindgen::{closure::Closure, JsCast};
110pub use wasm_bindgen_futures::{self, spawn_local, JsFuture};
111
112#[macro_use]
113pub mod shortcuts;
114pub mod app;
115pub mod browser;
116pub mod dom_entity_names;
117pub mod helpers;
118pub mod virtual_dom;
119
120/// Create an element flagged in a way that it will not be rendered. Useful
121/// in ternary operations.
122pub const fn empty<Ms>() -> virtual_dom::Node<Ms> {
123    virtual_dom::Node::Empty
124}
125
126#[deprecated(
127    since = "0.8.0",
128    note = "use [`Orders::stream`](app/orders/trait.Orders.html#method.stream) instead"
129)]
130/// A high-level wrapper for `web_sys::window.set_interval_with_callback_and_timeout_and_arguments_0`:
131///
132/// # References
133/// * [WASM bindgen closures](https://rustwasm.github.io/wasm-bindgen/examples/closures.html)
134/// * [`web_sys` Window](https://rustwasm.github.io/wasm-bindgen/api/web_sys/struct.Window.html)
135/// * [MDN docs](https://developer.mozilla.org/en-US/docs/Web/API/WindowOrWorkerGlobalScope/setInterval)
136pub fn set_interval(handler: Box<dyn Fn()>, timeout: i32) {
137    let callback = Closure::wrap(handler as Box<dyn Fn()>);
138    util::window()
139        .set_interval_with_callback_and_timeout_and_arguments_0(
140            callback.as_ref().unchecked_ref(),
141            timeout,
142        )
143        .expect("Problem setting interval");
144    callback.forget();
145}
146
147#[deprecated(
148    since = "0.8.0",
149    note = "use [`Orders::stream`](app/orders/trait.Orders.html#method.stream) instead"
150)]
151/// See [`set_interval`](fn.set_interval.html)
152///
153///
154/// # References
155/// * [MDN docs](https://developer.mozilla.org/en-US/docs/Wemb/API/WindowOrWorkerGlobalScope/setTimeout)
156pub fn set_timeout(handler: Box<dyn Fn()>, timeout: i32) {
157    let callback = Closure::wrap(handler as Box<dyn Fn()>);
158    util::window()
159        .set_timeout_with_callback_and_timeout_and_arguments_0(
160            callback.as_ref().unchecked_ref(),
161            timeout,
162        )
163        .expect("Problem setting timeout");
164    callback.forget();
165}
166
167/// Introduce `El` and `Tag` into the global namespace for convenience (`El` will be repeated
168/// often in the output type of components), and `UpdateEl`, which is required
169/// for element-creation macros, input event constructors, and the `History` struct.
170/// Expose the `wasm_bindgen` prelude.
171pub mod prelude {
172    #[cfg(feature = "routing")]
173    pub use crate::app::subs;
174    pub use crate::{
175        app::{
176            cmds, streams, App, CmdHandle, GetElement, MessageMapper, Orders, RenderInfo,
177            StreamHandle, SubHandle,
178        },
179        browser::dom::css_units::*,
180        browser::dom::event_handler::{
181            drag_ev, ev, input_ev, keyboard_ev, mouse_ev, pointer_ev, raw_ev, simple_ev, touch_ev,
182            wheel_ev,
183        },
184        browser::dom::Namespace,
185        browser::util::{
186            request_animation_frame, RequestAnimationFrameHandle, RequestAnimationFrameTime,
187        },
188        browser::{Url, UrlSearch},
189        helpers::not,
190        // macros are exported in crate root
191        // https://github.com/rust-lang-nursery/reference/blob/master/src/macros-by-example.md
192        shortcuts::*,
193        virtual_dom::{
194            el_key, el_ref::el_ref, on_insert, AsAtValue, At, AtValue, CSSValue, El, ElRef, Ev,
195            EventHandler, InsertEventHandler, IntoNodes, Node, St, Tag, ToClasses, UpdateEl,
196            UpdateElForIterator, UpdateElForOptionIterator, View,
197        },
198    };
199    pub use indexmap::IndexMap; // for attrs and style to work.
200    pub use js_sys;
201    pub use wasm_bindgen::{self, prelude::*, JsCast};
202    pub use web_sys;
203}
204
205#[cfg(test)]
206pub mod tests {
207    use wasm_bindgen_test::wasm_bindgen_test_configure;
208    wasm_bindgen_test_configure!(run_in_browser);
209
210    use wasm_bindgen_test::*;
211
212    /// This is a minimal app, that should build. Will fail if there's a breaking
213    /// change.
214    #[wasm_bindgen_test]
215    #[allow(dead_code)]
216    pub(crate) fn app_builds() {
217        use crate as seed; // required for macros to work.
218        use crate::app::Orders;
219        use crate::browser::dom::event_handler::mouse_ev;
220        use crate::prelude::*;
221        use crate::virtual_dom::{EventHandler, Node};
222
223        #[derive(Default)]
224        struct Model {
225            pub val: i32,
226        }
227
228        #[derive(Clone)]
229        enum Msg {
230            Increment,
231        }
232
233        fn update(msg: Msg, model: &mut Model, _: &mut impl Orders<Msg>) {
234            match msg {
235                Msg::Increment => model.val += 1,
236            }
237        }
238
239        fn view(_model: &Model) -> Vec<Node<Msg>> {
240            vec![div!["Hello world"]]
241        }
242
243        fn window_events(_model: &Model) -> Vec<EventHandler<Msg>> {
244            vec![mouse_ev("mousemove", |_| Msg::Increment)]
245        }
246
247        fn routes(_url: &seed::Url) -> Msg {
248            Msg::Increment
249        }
250
251        #[wasm_bindgen]
252        pub fn render() {
253            seed::App::start("render test app", |_, _| Model::default(), update, view);
254        }
255    }
256}