//#region node_modules/@tanstack/history/dist/esm/index.js var stateIndexKey = "__TSR_index"; var popStateEvent = "popstate"; var beforeUnloadEvent = "beforeunload"; function createHistory(opts) { let location = opts.getLocation(); const subscribers = /* @__PURE__ */ new Set(); const notify = (action) => { location = opts.getLocation(); subscribers.forEach((subscriber) => subscriber({ location, action })); }; const handleIndexChange = (action) => { if (opts.notifyOnIndexChange ?? true) notify(action); else location = opts.getLocation(); }; const tryNavigation = async ({ task, navigateOpts, ...actionInfo }) => { if (navigateOpts?.ignoreBlocker ?? false) { task(); return; } const blockers = opts.getBlockers?.() ?? []; const isPushOrReplace = actionInfo.type === "PUSH" || actionInfo.type === "REPLACE"; if (typeof document !== "undefined" && blockers.length && isPushOrReplace) for (const blocker of blockers) { const nextLocation = parseHref(actionInfo.path, actionInfo.state); if (await blocker.blockerFn({ currentLocation: location, nextLocation, action: actionInfo.type })) { opts.onBlocked?.(); return; } } task(); }; return { get location() { return location; }, get length() { return opts.getLength(); }, subscribers, subscribe: (cb) => { subscribers.add(cb); return () => { subscribers.delete(cb); }; }, push: (path, state, navigateOpts) => { const currentIndex = location.state[stateIndexKey]; state = assignKeyAndIndex(currentIndex + 1, state); tryNavigation({ task: () => { opts.pushState(path, state); notify({ type: "PUSH" }); }, navigateOpts, type: "PUSH", path, state }); }, replace: (path, state, navigateOpts) => { const currentIndex = location.state[stateIndexKey]; state = assignKeyAndIndex(currentIndex, state); tryNavigation({ task: () => { opts.replaceState(path, state); notify({ type: "REPLACE" }); }, navigateOpts, type: "REPLACE", path, state }); }, go: (index, navigateOpts) => { tryNavigation({ task: () => { opts.go(index); handleIndexChange({ type: "GO", index }); }, navigateOpts, type: "GO" }); }, back: (navigateOpts) => { tryNavigation({ task: () => { opts.back(navigateOpts?.ignoreBlocker ?? false); handleIndexChange({ type: "BACK" }); }, navigateOpts, type: "BACK" }); }, forward: (navigateOpts) => { tryNavigation({ task: () => { opts.forward(navigateOpts?.ignoreBlocker ?? false); handleIndexChange({ type: "FORWARD" }); }, navigateOpts, type: "FORWARD" }); }, canGoBack: () => location.state[stateIndexKey] !== 0, createHref: (str) => opts.createHref(str), block: (blocker) => { if (!opts.setBlockers) return () => {}; const blockers = opts.getBlockers?.() ?? []; opts.setBlockers([...blockers, blocker]); return () => { const blockers = opts.getBlockers?.() ?? []; opts.setBlockers?.(blockers.filter((b) => b !== blocker)); }; }, flush: () => opts.flush?.(), destroy: () => opts.destroy?.(), notify }; } function assignKeyAndIndex(index, state) { if (!state) state = {}; const key = createRandomKey(); return { ...state, key, __TSR_key: key, [stateIndexKey]: index }; } /** * Creates a history object that can be used to interact with the browser's * navigation. This is a lightweight API wrapping the browser's native methods. * It is designed to work with TanStack Router, but could be used as a standalone API as well. * IMPORTANT: This API implements history throttling via a microtask to prevent * excessive calls to the history API. In some browsers, calling history.pushState or * history.replaceState in quick succession can cause the browser to ignore subsequent * calls. This API smooths out those differences and ensures that your application * state will *eventually* match the browser state. In most cases, this is not a problem, * but if you need to ensure that the browser state is up to date, you can use the * `history.flush` method to immediately flush all pending state changes to the browser URL. * @param opts * @param opts.getHref A function that returns the current href (path + search + hash) * @param opts.createHref A function that takes a path and returns a href (path + search + hash) * @returns A history instance */ function createBrowserHistory(opts) { const win = opts?.window ?? (typeof document !== "undefined" ? window : void 0); const originalPushState = win.history.pushState; const originalReplaceState = win.history.replaceState; let blockers = []; const _getBlockers = () => blockers; const _setBlockers = (newBlockers) => blockers = newBlockers; const createHref = opts?.createHref ?? ((path) => path); const parseLocation = opts?.parseLocation ?? (() => parseHref(`${win.location.pathname}${win.location.search}${win.location.hash}`, win.history.state)); if (!win.history.state?.__TSR_key && !win.history.state?.key) { const addedKey = createRandomKey(); win.history.replaceState({ [stateIndexKey]: 0, key: addedKey, __TSR_key: addedKey }, ""); } let currentLocation = parseLocation(); let rollbackLocation; let nextPopIsGo = false; let ignoreNextPop = false; let skipBlockerNextPop = false; let ignoreNextBeforeUnload = false; const getLocation = () => currentLocation; let next; let scheduled; const flush = () => { if (!next) return; history._ignoreSubscribers = true; (next.isPush ? win.history.pushState : win.history.replaceState)(next.state, "", next.href); history._ignoreSubscribers = false; next = void 0; scheduled = void 0; rollbackLocation = void 0; }; const queueHistoryAction = (type, destHref, state) => { const href = createHref(destHref); if (!scheduled) rollbackLocation = currentLocation; currentLocation = parseHref(destHref, state); next = { href, state, isPush: next?.isPush || type === "push" }; if (!scheduled) scheduled = Promise.resolve().then(() => flush()); }; const onPushPop = (type) => { currentLocation = parseLocation(); history.notify({ type }); }; const onPushPopEvent = async () => { if (ignoreNextPop) { ignoreNextPop = false; return; } const nextLocation = parseLocation(); const delta = nextLocation.state[stateIndexKey] - currentLocation.state[stateIndexKey]; const isForward = delta === 1; const isBack = delta === -1; const isGo = !isForward && !isBack || nextPopIsGo; nextPopIsGo = false; const action = isGo ? "GO" : isBack ? "BACK" : "FORWARD"; const notify = isGo ? { type: "GO", index: delta } : { type: isBack ? "BACK" : "FORWARD" }; if (skipBlockerNextPop) skipBlockerNextPop = false; else { const blockers = _getBlockers(); if (typeof document !== "undefined" && blockers.length) { for (const blocker of blockers) if (await blocker.blockerFn({ currentLocation, nextLocation, action })) { ignoreNextPop = true; win.history.go(1); history.notify(notify); return; } } } currentLocation = parseLocation(); history.notify(notify); }; const onBeforeUnload = (e) => { if (ignoreNextBeforeUnload) { ignoreNextBeforeUnload = false; return; } let shouldBlock = false; const blockers = _getBlockers(); if (typeof document !== "undefined" && blockers.length) for (const blocker of blockers) { const shouldHaveBeforeUnload = blocker.enableBeforeUnload ?? true; if (shouldHaveBeforeUnload === true) { shouldBlock = true; break; } if (typeof shouldHaveBeforeUnload === "function" && shouldHaveBeforeUnload() === true) { shouldBlock = true; break; } } if (shouldBlock) { e.preventDefault(); return e.returnValue = ""; } }; const history = createHistory({ getLocation, getLength: () => win.history.length, pushState: (href, state) => queueHistoryAction("push", href, state), replaceState: (href, state) => queueHistoryAction("replace", href, state), back: (ignoreBlocker) => { if (ignoreBlocker) skipBlockerNextPop = true; ignoreNextBeforeUnload = true; return win.history.back(); }, forward: (ignoreBlocker) => { if (ignoreBlocker) skipBlockerNextPop = true; ignoreNextBeforeUnload = true; win.history.forward(); }, go: (n) => { nextPopIsGo = true; win.history.go(n); }, createHref: (href) => createHref(href), flush, destroy: () => { win.history.pushState = originalPushState; win.history.replaceState = originalReplaceState; win.removeEventListener(beforeUnloadEvent, onBeforeUnload, { capture: true }); win.removeEventListener(popStateEvent, onPushPopEvent); }, onBlocked: () => { if (rollbackLocation && currentLocation !== rollbackLocation) currentLocation = rollbackLocation; }, getBlockers: _getBlockers, setBlockers: _setBlockers, notifyOnIndexChange: false }); win.addEventListener(beforeUnloadEvent, onBeforeUnload, { capture: true }); win.addEventListener(popStateEvent, onPushPopEvent); win.history.pushState = function(...args) { const res = originalPushState.apply(win.history, args); if (!history._ignoreSubscribers) onPushPop("PUSH"); return res; }; win.history.replaceState = function(...args) { const res = originalReplaceState.apply(win.history, args); if (!history._ignoreSubscribers) onPushPop("REPLACE"); return res; }; return history; } /** * Create an in-memory history implementation. * Ideal for server rendering, tests, and non-DOM environments. * @link https://tanstack.com/router/latest/docs/framework/react/guide/history-types */ function createMemoryHistory(opts = { initialEntries: ["/"] }) { const entries = opts.initialEntries; let index = opts.initialIndex ? Math.min(Math.max(opts.initialIndex, 0), entries.length - 1) : entries.length - 1; const states = entries.map((_entry, index) => assignKeyAndIndex(index, void 0)); const getLocation = () => parseHref(entries[index], states[index]); let blockers = []; const _getBlockers = () => blockers; const _setBlockers = (newBlockers) => blockers = newBlockers; return createHistory({ getLocation, getLength: () => entries.length, pushState: (path, state) => { if (index < entries.length - 1) { entries.splice(index + 1); states.splice(index + 1); } states.push(state); entries.push(path); index = Math.max(entries.length - 1, 0); }, replaceState: (path, state) => { states[index] = state; entries[index] = path; }, back: () => { index = Math.max(index - 1, 0); }, forward: () => { index = Math.min(index + 1, entries.length - 1); }, go: (n) => { index = Math.min(Math.max(index + n, 0), entries.length - 1); }, createHref: (path) => path, getBlockers: _getBlockers, setBlockers: _setBlockers }); } /** * Sanitize a path to prevent open redirect vulnerabilities. * Removes control characters and collapses leading double slashes. */ function sanitizePath(path) { let sanitized = path.replace(/[\x00-\x1f\x7f]/g, ""); if (sanitized.startsWith("//")) sanitized = "/" + sanitized.replace(/^\/+/, ""); return sanitized; } function parseHref(href, state) { const sanitizedHref = sanitizePath(href); const hashIndex = sanitizedHref.indexOf("#"); const searchIndex = sanitizedHref.indexOf("?"); const addedKey = createRandomKey(); return { href: sanitizedHref, pathname: sanitizedHref.substring(0, hashIndex > 0 ? searchIndex > 0 ? Math.min(hashIndex, searchIndex) : hashIndex : searchIndex > 0 ? searchIndex : sanitizedHref.length), hash: hashIndex > -1 ? sanitizedHref.substring(hashIndex) : "", search: searchIndex > -1 ? sanitizedHref.slice(searchIndex, hashIndex === -1 ? void 0 : hashIndex) : "", state: state || { [stateIndexKey]: 0, key: addedKey, __TSR_key: addedKey } }; } function createRandomKey() { return (Math.random() + 1).toString(36).substring(7); } //#endregion export { createMemoryHistory as n, parseHref as r, createBrowserHistory as t };