dioxus_history/
lib.rs

1use dioxus_core::prelude::{provide_context, provide_root_context};
2use std::{rc::Rc, sync::Arc};
3
4mod memory;
5pub use memory::*;
6
7/// Get the history provider for the current platform if the platform doesn't implement a history functionality.
8pub fn history() -> Rc<dyn History> {
9    match dioxus_core::prelude::try_consume_context::<Rc<dyn History>>() {
10        Some(history) => history,
11        None => {
12            tracing::error!("Unable to find a history provider in the renderer. Make sure your renderer supports the Router. Falling back to the in-memory history provider.");
13            provide_root_context(Rc::new(MemoryHistory::default()))
14        }
15    }
16}
17
18/// Provide a history context to the current component.
19pub fn provide_history_context(history: Rc<dyn History>) {
20    provide_context(history);
21}
22
23pub trait History {
24    /// Get the path of the current URL.
25    ///
26    /// **Must start** with `/`. **Must _not_ contain** the prefix.
27    ///
28    /// ```rust
29    /// # use dioxus::prelude::*;
30    /// # #[component]
31    /// # fn Index() -> Element { VNode::empty() }
32    /// # #[component]
33    /// # fn OtherPage() -> Element { VNode::empty() }
34    /// #[derive(Clone, Routable, Debug, PartialEq)]
35    /// enum Route {
36    ///     #[route("/")]
37    ///     Index {},
38    ///     #[route("/some-other-page")]
39    ///     OtherPage {},
40    /// }
41    /// let mut history = dioxus::history::MemoryHistory::default();
42    /// assert_eq!(history.current_route(), "/");
43    ///
44    /// history.push(Route::OtherPage {}.to_string());
45    /// assert_eq!(history.current_route(), "/some-other-page");
46    /// ```
47    #[must_use]
48    fn current_route(&self) -> String;
49
50    /// Get the current path prefix of the URL.
51    ///
52    /// Not all [`HistoryProvider`]s need a prefix feature. It is meant for environments where a
53    /// dioxus-router-core-routed application is not running on `/`. The [`HistoryProvider`] is responsible
54    /// for removing the prefix from the dioxus-router-core-internal path, and also for adding it back in
55    /// during navigation. This functions value is only used for creating `href`s (e.g. for SSR or
56    /// display (but not navigation) in a web app).
57    fn current_prefix(&self) -> Option<String> {
58        None
59    }
60
61    /// Check whether there is a previous page to navigate back to.
62    ///
63    /// If a [`HistoryProvider`] cannot know this, it should return [`true`].
64    ///
65    /// ```rust
66    /// # use dioxus::prelude::*;
67    /// # #[component]
68    /// # fn Index() -> Element { VNode::empty() }
69    /// # fn Other() -> Element { VNode::empty() }
70    /// #[derive(Clone, Routable, Debug, PartialEq)]
71    /// enum Route {
72    ///     #[route("/")]
73    ///     Index {},
74    ///     #[route("/other")]
75    ///     Other {},
76    /// }
77    /// let mut history = dioxus::history::MemoryHistory::default();
78    /// assert_eq!(history.can_go_back(), false);
79    ///
80    /// history.push(Route::Other {}.to_string());
81    /// assert_eq!(history.can_go_back(), true);
82    /// ```
83    #[must_use]
84    fn can_go_back(&self) -> bool {
85        true
86    }
87
88    /// Go back to a previous page.
89    ///
90    /// If a [`HistoryProvider`] cannot go to a previous page, it should do nothing. This method
91    /// might be called, even if `can_go_back` returns [`false`].
92    ///
93    /// ```rust
94    /// # use dioxus::prelude::*;
95    /// # #[component]
96    /// # fn Index() -> Element { VNode::empty() }
97    /// # #[component]
98    /// # fn OtherPage() -> Element { VNode::empty() }
99    /// #[derive(Clone, Routable, Debug, PartialEq)]
100    /// enum Route {
101    ///     #[route("/")]
102    ///     Index {},
103    ///     #[route("/some-other-page")]
104    ///     OtherPage {},
105    /// }
106    /// let mut history = dioxus::history::MemoryHistory::default();
107    /// assert_eq!(history.current_route(), "/");
108    ///
109    /// history.go_back();
110    /// assert_eq!(history.current_route(), "/");
111    ///
112    /// history.push(Route::OtherPage {}.to_string());
113    /// assert_eq!(history.current_route(), "/some-other-page");
114    ///
115    /// history.go_back();
116    /// assert_eq!(history.current_route(), "/");
117    /// ```
118    fn go_back(&self);
119
120    /// Check whether there is a future page to navigate forward to.
121    ///
122    /// If a [`HistoryProvider`] cannot know this, it should return [`true`].
123    ///
124    /// ```rust
125    /// # use dioxus::prelude::*;
126    /// # #[component]
127    /// # fn Index() -> Element { VNode::empty() }
128    /// # #[component]
129    /// # fn OtherPage() -> Element { VNode::empty() }
130    /// #[derive(Clone, Routable, Debug, PartialEq)]
131    /// enum Route {
132    ///     #[route("/")]
133    ///     Index {},
134    ///     #[route("/some-other-page")]
135    ///     OtherPage {},
136    /// }
137    /// let mut history = dioxus::history::MemoryHistory::default();
138    /// assert_eq!(history.can_go_forward(), false);
139    ///
140    /// history.push(Route::OtherPage {}.to_string());
141    /// assert_eq!(history.can_go_forward(), false);
142    ///
143    /// history.go_back();
144    /// assert_eq!(history.can_go_forward(), true);
145    /// ```
146    #[must_use]
147    fn can_go_forward(&self) -> bool {
148        true
149    }
150
151    /// Go forward to a future page.
152    ///
153    /// If a [`HistoryProvider`] cannot go to a previous page, it should do nothing. This method
154    /// might be called, even if `can_go_forward` returns [`false`].
155    ///
156    /// ```rust
157    /// # use dioxus::prelude::*;
158    /// # #[component]
159    /// # fn Index() -> Element { VNode::empty() }
160    /// # #[component]
161    /// # fn OtherPage() -> Element { VNode::empty() }
162    /// #[derive(Clone, Routable, Debug, PartialEq)]
163    /// enum Route {
164    ///     #[route("/")]
165    ///     Index {},
166    ///     #[route("/some-other-page")]
167    ///     OtherPage {},
168    /// }
169    /// let mut history = dioxus::history::MemoryHistory::default();
170    /// history.push(Route::OtherPage {}.to_string());
171    /// assert_eq!(history.current_route(), Route::OtherPage {}.to_string());
172    ///
173    /// history.go_back();
174    /// assert_eq!(history.current_route(), Route::Index {}.to_string());
175    ///
176    /// history.go_forward();
177    /// assert_eq!(history.current_route(), Route::OtherPage {}.to_string());
178    /// ```
179    fn go_forward(&self);
180
181    /// Go to another page.
182    ///
183    /// This should do three things:
184    /// 1. Merge the current URL with the `path` parameter (which may also include a query part).
185    /// 2. Remove the previous URL to the navigation history.
186    /// 3. Clear the navigation future.
187    ///
188    /// ```rust
189    /// # use dioxus::prelude::*;
190    /// # #[component]
191    /// # fn Index() -> Element { VNode::empty() }
192    /// # #[component]
193    /// # fn OtherPage() -> Element { VNode::empty() }
194    /// #[derive(Clone, Routable, Debug, PartialEq)]
195    /// enum Route {
196    ///     #[route("/")]
197    ///     Index {},
198    ///     #[route("/some-other-page")]
199    ///     OtherPage {},
200    /// }
201    /// let mut history = dioxus::history::MemoryHistory::default();
202    /// assert_eq!(history.current_route(), Route::Index {}.to_string());
203    ///
204    /// history.push(Route::OtherPage {}.to_string());
205    /// assert_eq!(history.current_route(), Route::OtherPage {}.to_string());
206    /// assert!(history.can_go_back());
207    /// ```
208    fn push(&self, route: String);
209
210    /// Replace the current page with another one.
211    ///
212    /// This should merge the current URL with the `path` parameter (which may also include a query
213    /// part). In contrast to the `push` function, the navigation history and future should stay
214    /// untouched.
215    ///
216    /// ```rust
217    /// # use dioxus::prelude::*;
218    /// # #[component]
219    /// # fn Index() -> Element { VNode::empty() }
220    /// # #[component]
221    /// # fn OtherPage() -> Element { VNode::empty() }
222    /// #[derive(Clone, Routable, Debug, PartialEq)]
223    /// enum Route {
224    ///     #[route("/")]
225    ///     Index {},
226    ///     #[route("/some-other-page")]
227    ///     OtherPage {},
228    /// }
229    /// let mut history = dioxus::history::MemoryHistory::default();
230    /// assert_eq!(history.current_route(), Route::Index {}.to_string());
231    ///
232    /// history.replace(Route::OtherPage {}.to_string());
233    /// assert_eq!(history.current_route(), Route::OtherPage {}.to_string());
234    /// assert!(!history.can_go_back());
235    /// ```
236    fn replace(&self, path: String);
237
238    /// Navigate to an external URL.
239    ///
240    /// This should navigate to an external URL, which isn't controlled by the router. If a
241    /// [`HistoryProvider`] cannot do that, it should return [`false`], otherwise [`true`].
242    ///
243    /// Returning [`false`] will cause the router to handle the external navigation failure.
244    #[allow(unused_variables)]
245    fn external(&self, url: String) -> bool {
246        false
247    }
248
249    /// Provide the [`HistoryProvider`] with an update callback.
250    ///
251    /// Some [`HistoryProvider`]s may receive URL updates from outside the router. When such
252    /// updates are received, they should call `callback`, which will cause the router to update.
253    #[allow(unused_variables)]
254    fn updater(&self, callback: Arc<dyn Fn() + Send + Sync>) {}
255
256    /// Whether the router should include the legacy prevent default attribute instead of the new
257    /// prevent default method. This should only be used by liveview.
258    fn include_prevent_default(&self) -> bool {
259        false
260    }
261}