UNPKG

11.8 kBJavaScriptView Raw
1/**
2 * react-router v8.3.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* @public
235* @category Utils
236* @param path The pathname, search, and hash components to combine.
237* @returns The combined URL path.
238*/
239function createPath({ pathname = "/", search = "", hash = "" }) {
240 if (search && search !== "?") pathname += search.charAt(0) === "?" ? search : "?" + search;
241 if (hash && hash !== "#") pathname += hash.charAt(0) === "#" ? hash : "#" + hash;
242 return pathname;
243}
244/**
245* Parses a string URL path into its separate pathname, search, and hash components.
246*
247* @public
248* @category Utils
249* @param path The URL path to parse.
250* @returns The parsed pathname, search, and hash components.
251*/
252function parsePath(path) {
253 let parsedPath = {};
254 if (path) {
255 let hashIndex = path.indexOf("#");
256 if (hashIndex >= 0) {
257 parsedPath.hash = path.substring(hashIndex);
258 path = path.substring(0, hashIndex);
259 }
260 let searchIndex = path.indexOf("?");
261 if (searchIndex >= 0) {
262 parsedPath.search = path.substring(searchIndex);
263 path = path.substring(0, searchIndex);
264 }
265 if (path) parsedPath.pathname = path;
266 }
267 return parsedPath;
268}
269function getUrlBasedHistory(getLocation, createHref, validateLocation, options = {}) {
270 let { window = document.defaultView, v5Compat = false } = options;
271 let globalHistory = window.history;
272 let action = "POP";
273 let listener = null;
274 let index = getIndex();
275 if (index == null) {
276 index = 0;
277 globalHistory.replaceState({
278 ...globalHistory.state,
279 idx: index
280 }, "");
281 }
282 function getIndex() {
283 return (globalHistory.state || { idx: null }).idx;
284 }
285 function handlePop() {
286 action = "POP";
287 let nextIndex = getIndex();
288 let delta = nextIndex == null ? null : nextIndex - index;
289 index = nextIndex;
290 if (listener) listener({
291 action,
292 location: history.location,
293 delta
294 });
295 }
296 function push(to, state) {
297 action = "PUSH";
298 let location = isLocation(to) ? to : createLocation(history.location, to, state);
299 if (validateLocation) validateLocation(location, to);
300 index = getIndex() + 1;
301 let historyState = getHistoryState(location, index);
302 let url = history.createHref(location.mask || location);
303 try {
304 globalHistory.pushState(historyState, "", url);
305 } catch (error) {
306 if (error instanceof DOMException && error.name === "DataCloneError") throw error;
307 window.location.assign(url);
308 }
309 if (v5Compat && listener) listener({
310 action,
311 location: history.location,
312 delta: 1
313 });
314 }
315 function replace(to, state) {
316 action = "REPLACE";
317 let location = isLocation(to) ? to : createLocation(history.location, to, state);
318 if (validateLocation) validateLocation(location, to);
319 index = getIndex();
320 let historyState = getHistoryState(location, index);
321 let url = history.createHref(location.mask || location);
322 globalHistory.replaceState(historyState, "", url);
323 if (v5Compat && listener) listener({
324 action,
325 location: history.location,
326 delta: 0
327 });
328 }
329 function createURL(to) {
330 return createBrowserURLImpl(window, to);
331 }
332 let history = {
333 get action() {
334 return action;
335 },
336 get location() {
337 return getLocation(window, globalHistory);
338 },
339 listen(fn) {
340 if (listener) throw new Error("A history only accepts one active listener");
341 window.addEventListener(PopStateEventType, handlePop);
342 listener = fn;
343 return () => {
344 window.removeEventListener(PopStateEventType, handlePop);
345 listener = null;
346 };
347 },
348 createHref(to) {
349 return createHref(window, to);
350 },
351 createURL,
352 encodeLocation(to) {
353 let url = createURL(to);
354 return {
355 pathname: url.pathname,
356 search: url.search,
357 hash: url.hash
358 };
359 },
360 push,
361 replace,
362 go(n) {
363 return globalHistory.go(n);
364 }
365 };
366 return history;
367}
368function createBrowserURLImpl(windowImpl, to, isAbsolute = false) {
369 let base = "http://localhost";
370 if (windowImpl) base = windowImpl.location.origin !== "null" ? windowImpl.location.origin : windowImpl.location.href;
371 invariant(base, "No window.location.(origin|href) available to create URL");
372 let href = typeof to === "string" ? to : createPath(to);
373 href = href.replace(/ $/, "%20");
374 if (!isAbsolute && PROTOCOL_RELATIVE_URL_REGEX.test(href)) href = base + href;
375 return new URL(href, base);
376}
377//#endregion
378export { Action, createBrowserHistory, createBrowserURLImpl, createHashHistory, createLocation, createMemoryHistory, createPath, invariant, parsePath, warning };