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}