| 1 | |
| 2 | |
| 3 | |
| 4 | |
| 5 | |
| 6 | |
| 7 | |
| 8 | |
| 9 | |
| 10 |
|
| 11 | import { PROTOCOL_RELATIVE_URL_REGEX } from "./url.js";
|
| 12 |
|
| 13 | |
| 14 | |
| 15 |
|
| 16 | let Action = function(Action) {
|
| 17 | |
| 18 | |
| 19 | |
| 20 | |
| 21 | |
| 22 | |
| 23 |
|
| 24 | Action["Pop"] = "POP";
|
| 25 | |
| 26 | |
| 27 | |
| 28 | |
| 29 |
|
| 30 | Action["Push"] = "PUSH";
|
| 31 | |
| 32 | |
| 33 | |
| 34 |
|
| 35 | Action["Replace"] = "REPLACE";
|
| 36 | return Action;
|
| 37 | }({});
|
| 38 | const PopStateEventType = "popstate";
|
| 39 | function 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 | |
| 44 | |
| 45 |
|
| 46 | function 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 | |
| 131 | |
| 132 | |
| 133 | |
| 134 | |
| 135 |
|
| 136 | function 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 | |
| 157 | |
| 158 | |
| 159 | |
| 160 | |
| 161 | |
| 162 |
|
| 163 | function 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 | }
|
| 188 | function invariant(value, message) {
|
| 189 | if (value === false || value === null || typeof value === "undefined") throw new Error(message);
|
| 190 | }
|
| 191 | function 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 | }
|
| 199 | function createKey() {
|
| 200 | return Math.random().toString(36).substring(2, 10);
|
| 201 | }
|
| 202 | |
| 203 | |
| 204 |
|
| 205 | function 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 | |
| 219 |
|
| 220 | function 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 | |
| 233 | |
| 234 | |
| 235 |
|
| 236 | function 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 | |
| 243 | |
| 244 | |
| 245 |
|
| 246 | function 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 | }
|
| 263 | function 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 | }
|
| 362 | function 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 |
|
| 372 | export { Action, createBrowserHistory, createBrowserURLImpl, createHashHistory, createLocation, createMemoryHistory, createPath, invariant, parsePath, warning };
|