UNPKG

11.6 kBJavaScriptView Raw
1/**
2 * react-router v8.0.0
3 *
4 * Copyright (c) Remix Software Inc.
5 *
6 * This source code is licensed under the MIT license found in the
7 * LICENSE.md file in the root directory of this source tree.
8 *
9 * @license MIT
10 */
11import { PROTOCOL_RELATIVE_URL_REGEX } from "./url.js";
12//#region lib/router/history.ts
13/**
14* Actions represent the type of change to a location value.
15*/
16let Action = /* @__PURE__ */ function(Action) {
17 /**
18 * A POP indicates a change to an arbitrary index in the history stack, such
19 * as a back or forward navigation. It does not describe the direction of the
20 * navigation, only that the current index changed.
21 *
22 * Note: This is the default action for newly created history objects.
23 */
24 Action["Pop"] = "POP";
25 /**
26 * A PUSH indicates a new entry being added to the history stack, such as when
27 * a link is clicked and a new page loads. When this happens, all subsequent
28 * entries in the stack are lost.
29 */
30 Action["Push"] = "PUSH";
31 /**
32 * A REPLACE indicates the entry at the current index in the history stack
33 * being replaced by a new one.
34 */
35 Action["Replace"] = "REPLACE";
36 return Action;
37}({});
38const PopStateEventType = "popstate";
39function isLocation(obj) {
40 return typeof obj === "object" && obj != null && "pathname" in obj && "search" in obj && "hash" in obj && "state" in obj && "key" in obj;
41}
42/**
43* Memory history stores the current location in memory. It is designed for use
44* in stateful non-browser environments like tests and React Native.
45*/
46function createMemoryHistory(options = {}) {
47 let { initialEntries = ["/"], initialIndex, v5Compat = false } = options;
48 let entries;
49 entries = initialEntries.map((entry, index) => createMemoryLocation(entry, typeof entry === "string" ? null : entry.state, index === 0 ? "default" : void 0, typeof entry === "string" ? void 0 : entry.mask));
50 let index = clampIndex(initialIndex == null ? entries.length - 1 : initialIndex);
51 let action = "POP";
52 let listener = null;
53 function clampIndex(n) {
54 return Math.min(Math.max(n, 0), entries.length - 1);
55 }
56 function getCurrentLocation() {
57 return entries[index];
58 }
59 function createMemoryLocation(to, state = null, key, mask) {
60 let location = createLocation(entries ? getCurrentLocation().pathname : "/", to, state, key, mask);
61 warning(location.pathname.charAt(0) === "/", `relative pathnames are not supported in memory history: ${JSON.stringify(to)}`);
62 return location;
63 }
64 function createHref(to) {
65 return typeof to === "string" ? to : createPath(to);
66 }
67 return {
68 get index() {
69 return index;
70 },
71 get action() {
72 return action;
73 },
74 get location() {
75 return getCurrentLocation();
76 },
77 createHref,
78 createURL(to) {
79 return new URL(createHref(to), "http://localhost");
80 },
81 encodeLocation(to) {
82 let path = typeof to === "string" ? parsePath(to) : to;
83 return {
84 pathname: path.pathname || "",
85 search: path.search || "",
86 hash: path.hash || ""
87 };
88 },
89 push(to, state) {
90 action = "PUSH";
91 let nextLocation = isLocation(to) ? to : createMemoryLocation(to, state);
92 index += 1;
93 entries.splice(index, entries.length, nextLocation);
94 if (v5Compat && listener) listener({
95 action,
96 location: nextLocation,
97 delta: 1
98 });
99 },
100 replace(to, state) {
101 action = "REPLACE";
102 let nextLocation = isLocation(to) ? to : createMemoryLocation(to, state);
103 entries[index] = nextLocation;
104 if (v5Compat && listener) listener({
105 action,
106 location: nextLocation,
107 delta: 0
108 });
109 },
110 go(delta) {
111 action = "POP";
112 let nextIndex = clampIndex(index + delta);
113 let nextLocation = entries[nextIndex];
114 index = nextIndex;
115 if (listener) listener({
116 action,
117 location: nextLocation,
118 delta
119 });
120 },
121 listen(fn) {
122 listener = fn;
123 return () => {
124 listener = null;
125 };
126 }
127 };
128}
129/**
130* Browser history stores the location in regular URLs. This is the standard for
131* most web apps, but it requires some configuration on the server to ensure you
132* serve the same app at multiple URLs.
133*
134* @see https://github.com/remix-run/history/tree/main/docs/api-reference.md#createbrowserhistory
135*/
136function createBrowserHistory(options = {}) {
137 function createBrowserLocation(window, globalHistory) {
138 let maskedLocation = globalHistory.state?.masked;
139 let { pathname, search, hash } = maskedLocation || window.location;
140 return createLocation("", {
141 pathname,
142 search,
143 hash
144 }, globalHistory.state && globalHistory.state.usr || null, globalHistory.state && globalHistory.state.key || "default", maskedLocation ? {
145 pathname: window.location.pathname,
146 search: window.location.search,
147 hash: window.location.hash
148 } : void 0);
149 }
150 function createBrowserHref(window, to) {
151 return typeof to === "string" ? to : createPath(to);
152 }
153 return getUrlBasedHistory(createBrowserLocation, createBrowserHref, null, options);
154}
155/**
156* Hash history stores the location in window.location.hash. This makes it ideal
157* for situations where you don't want to send the location to the server for
158* some reason, either because you do cannot configure it or the URL space is
159* reserved for something else.
160*
161* @see https://github.com/remix-run/history/tree/main/docs/api-reference.md#createhashhistory
162*/
163function createHashHistory(options = {}) {
164 function createHashLocation(window, globalHistory) {
165 let { pathname = "/", search = "", hash = "" } = parsePath(window.location.hash.substring(1));
166 if (!pathname.startsWith("/") && !pathname.startsWith(".")) pathname = "/" + pathname;
167 return createLocation("", {
168 pathname,
169 search,
170 hash
171 }, globalHistory.state && globalHistory.state.usr || null, globalHistory.state && globalHistory.state.key || "default");
172 }
173 function createHashHref(window, to) {
174 let base = window.document.querySelector("base");
175 let href = "";
176 if (base && base.getAttribute("href")) {
177 let url = window.location.href;
178 let hashIndex = url.indexOf("#");
179 href = hashIndex === -1 ? url : url.slice(0, hashIndex);
180 }
181 return href + "#" + (typeof to === "string" ? to : createPath(to));
182 }
183 function validateHashLocation(location, to) {
184 warning(location.pathname.charAt(0) === "/", `relative pathnames are not supported in hash history.push(${JSON.stringify(to)})`);
185 }
186 return getUrlBasedHistory(createHashLocation, createHashHref, validateHashLocation, options);
187}
188function invariant(value, message) {
189 if (value === false || value === null || typeof value === "undefined") throw new Error(message);
190}
191function warning(cond, message) {
192 if (!cond) {
193 if (typeof console !== "undefined") console.warn(message);
194 try {
195 throw new Error(message);
196 } catch (e) {}
197 }
198}
199function createKey() {
200 return Math.random().toString(36).substring(2, 10);
201}
202/**
203* For browser-based histories, we combine the state and key into an object
204*/
205function getHistoryState(location, index) {
206 return {
207 usr: location.state,
208 key: location.key,
209 idx: index,
210 masked: location.mask ? {
211 pathname: location.pathname,
212 search: location.search,
213 hash: location.hash
214 } : void 0
215 };
216}
217/**
218* Creates a Location object with a unique key from the given Path
219*/
220function createLocation(current, to, state = null, key, mask) {
221 return {
222 pathname: typeof current === "string" ? current : current.pathname,
223 search: "",
224 hash: "",
225 ...typeof to === "string" ? parsePath(to) : to,
226 state,
227 key: to && to.key || key || createKey(),
228 mask
229 };
230}
231/**
232* Creates a string URL path from the given pathname, search, and hash components.
233*
234* @category Utils
235*/
236function createPath({ pathname = "/", search = "", hash = "" }) {
237 if (search && search !== "?") pathname += search.charAt(0) === "?" ? search : "?" + search;
238 if (hash && hash !== "#") pathname += hash.charAt(0) === "#" ? hash : "#" + hash;
239 return pathname;
240}
241/**
242* Parses a string URL path into its separate pathname, search, and hash components.
243*
244* @category Utils
245*/
246function parsePath(path) {
247 let parsedPath = {};
248 if (path) {
249 let hashIndex = path.indexOf("#");
250 if (hashIndex >= 0) {
251 parsedPath.hash = path.substring(hashIndex);
252 path = path.substring(0, hashIndex);
253 }
254 let searchIndex = path.indexOf("?");
255 if (searchIndex >= 0) {
256 parsedPath.search = path.substring(searchIndex);
257 path = path.substring(0, searchIndex);
258 }
259 if (path) parsedPath.pathname = path;
260 }
261 return parsedPath;
262}
263function getUrlBasedHistory(getLocation, createHref, validateLocation, options = {}) {
264 let { window = document.defaultView, v5Compat = false } = options;
265 let globalHistory = window.history;
266 let action = "POP";
267 let listener = null;
268 let index = getIndex();
269 if (index == null) {
270 index = 0;
271 globalHistory.replaceState({
272 ...globalHistory.state,
273 idx: index
274 }, "");
275 }
276 function getIndex() {
277 return (globalHistory.state || { idx: null }).idx;
278 }
279 function handlePop() {
280 action = "POP";
281 let nextIndex = getIndex();
282 let delta = nextIndex == null ? null : nextIndex - index;
283 index = nextIndex;
284 if (listener) listener({
285 action,
286 location: history.location,
287 delta
288 });
289 }
290 function push(to, state) {
291 action = "PUSH";
292 let location = isLocation(to) ? to : createLocation(history.location, to, state);
293 if (validateLocation) validateLocation(location, to);
294 index = getIndex() + 1;
295 let historyState = getHistoryState(location, index);
296 let url = history.createHref(location.mask || location);
297 try {
298 globalHistory.pushState(historyState, "", url);
299 } catch (error) {
300 if (error instanceof DOMException && error.name === "DataCloneError") throw error;
301 window.location.assign(url);
302 }
303 if (v5Compat && listener) listener({
304 action,
305 location: history.location,
306 delta: 1
307 });
308 }
309 function replace(to, state) {
310 action = "REPLACE";
311 let location = isLocation(to) ? to : createLocation(history.location, to, state);
312 if (validateLocation) validateLocation(location, to);
313 index = getIndex();
314 let historyState = getHistoryState(location, index);
315 let url = history.createHref(location.mask || location);
316 globalHistory.replaceState(historyState, "", url);
317 if (v5Compat && listener) listener({
318 action,
319 location: history.location,
320 delta: 0
321 });
322 }
323 function createURL(to) {
324 return createBrowserURLImpl(window, to);
325 }
326 let history = {
327 get action() {
328 return action;
329 },
330 get location() {
331 return getLocation(window, globalHistory);
332 },
333 listen(fn) {
334 if (listener) throw new Error("A history only accepts one active listener");
335 window.addEventListener(PopStateEventType, handlePop);
336 listener = fn;
337 return () => {
338 window.removeEventListener(PopStateEventType, handlePop);
339 listener = null;
340 };
341 },
342 createHref(to) {
343 return createHref(window, to);
344 },
345 createURL,
346 encodeLocation(to) {
347 let url = createURL(to);
348 return {
349 pathname: url.pathname,
350 search: url.search,
351 hash: url.hash
352 };
353 },
354 push,
355 replace,
356 go(n) {
357 return globalHistory.go(n);
358 }
359 };
360 return history;
361}
362function createBrowserURLImpl(windowImpl, to, isAbsolute = false) {
363 let base = "http://localhost";
364 if (windowImpl) base = windowImpl.location.origin !== "null" ? windowImpl.location.origin : windowImpl.location.href;
365 invariant(base, "No window.location.(origin|href) available to create URL");
366 let href = typeof to === "string" ? to : createPath(to);
367 href = href.replace(/ $/, "%20");
368 if (!isAbsolute && PROTOCOL_RELATIVE_URL_REGEX.test(href)) href = base + href;
369 return new URL(href, base);
370}
371//#endregion
372export { Action, createBrowserHistory, createBrowserURLImpl, createHashHistory, createLocation, createMemoryHistory, createPath, invariant, parsePath, warning };