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