@tanstack/history
Advanced tools
+390
-404
@@ -1,420 +0,405 @@ | ||
| "use strict"; | ||
| Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" }); | ||
| const stateIndexKey = "__TSR_index"; | ||
| const popStateEvent = "popstate"; | ||
| const beforeUnloadEvent = "beforeunload"; | ||
| //#region src/index.ts | ||
| 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 | ||
| }) => { | ||
| const ignoreBlocker = navigateOpts?.ignoreBlocker ?? false; | ||
| if (ignoreBlocker) { | ||
| 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); | ||
| const isBlocked = await blocker.blockerFn({ | ||
| currentLocation: location, | ||
| nextLocation, | ||
| action: actionInfo.type | ||
| }); | ||
| if (isBlocked) { | ||
| 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 blockers2 = opts.getBlockers?.() ?? []; | ||
| opts.setBlockers?.(blockers2.filter((b) => b !== blocker)); | ||
| }; | ||
| }, | ||
| flush: () => opts.flush?.(), | ||
| destroy: () => opts.destroy?.(), | ||
| notify | ||
| }; | ||
| 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, | ||
| // TODO: Remove in v2 - use __TSR_key instead | ||
| __TSR_key: key, | ||
| [stateIndexKey]: index | ||
| }; | ||
| 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, | ||
| // TODO: Remove in v2 - use __TSR_key instead | ||
| __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 blockers2 = _getBlockers(); | ||
| if (typeof document !== "undefined" && blockers2.length) { | ||
| for (const blocker of blockers2) { | ||
| const isBlocked = await blocker.blockerFn({ | ||
| currentLocation, | ||
| nextLocation, | ||
| action | ||
| }); | ||
| if (isBlocked) { | ||
| 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 blockers2 = _getBlockers(); | ||
| if (typeof document !== "undefined" && blockers2.length) { | ||
| for (const blocker of blockers2) { | ||
| 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 = ""; | ||
| } | ||
| return; | ||
| }; | ||
| 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; | ||
| 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 a hash-based history implementation. | ||
| * Useful for static hosts or environments without server URL rewriting. | ||
| * @link https://tanstack.com/router/latest/docs/framework/react/guide/history-types | ||
| */ | ||
| function createHashHistory(opts) { | ||
| const win = opts?.window ?? (typeof document !== "undefined" ? window : void 0); | ||
| return createBrowserHistory({ | ||
| window: win, | ||
| parseLocation: () => { | ||
| const hashSplit = win.location.hash.split("#").slice(1); | ||
| const pathPart = hashSplit[0] ?? "/"; | ||
| const searchPart = win.location.search; | ||
| const hashEntries = hashSplit.slice(1); | ||
| const hashPart = hashEntries.length === 0 ? "" : `#${hashEntries.join("#")}`; | ||
| const hashHref = `${pathPart}${searchPart}${hashPart}`; | ||
| return parseHref(hashHref, win.history.state); | ||
| }, | ||
| createHref: (href) => `${win.location.pathname}${win.location.search}#${href}` | ||
| }); | ||
| const win = opts?.window ?? (typeof document !== "undefined" ? window : void 0); | ||
| return createBrowserHistory({ | ||
| window: win, | ||
| parseLocation: () => { | ||
| const hashSplit = win.location.hash.split("#").slice(1); | ||
| const pathPart = hashSplit[0] ?? "/"; | ||
| const searchPart = win.location.search; | ||
| const hashEntries = hashSplit.slice(1); | ||
| return parseHref(`${pathPart}${searchPart}${hashEntries.length === 0 ? "" : `#${hashEntries.join("#")}`}`, win.history.state); | ||
| }, | ||
| createHref: (href) => `${win.location.pathname}${win.location.search}#${href}` | ||
| }); | ||
| } | ||
| 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, index2) => assignKeyAndIndex(index2, 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 | ||
| }); | ||
| /** | ||
| * 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; | ||
| 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 } | ||
| }; | ||
| 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); | ||
| return (Math.random() + 1).toString(36).substring(7); | ||
| } | ||
| //#endregion | ||
| exports.createBrowserHistory = createBrowserHistory; | ||
@@ -425,2 +410,3 @@ exports.createHashHistory = createHashHistory; | ||
| exports.parseHref = parseHref; | ||
| //# sourceMappingURL=index.cjs.map | ||
| //# sourceMappingURL=index.cjs.map |
@@ -1,1 +0,1 @@ | ||
| {"version":3,"file":"index.cjs","sources":["../../src/index.ts"],"sourcesContent":["// While the public API was clearly inspired by the \"history\" npm package,\n// This implementation attempts to be more lightweight by\n// making assumptions about the way TanStack Router works\n\nexport interface NavigateOptions {\n ignoreBlocker?: boolean\n}\n\ntype SubscriberHistoryAction =\n | {\n type: Exclude<HistoryAction, 'GO'>\n }\n | {\n type: 'GO'\n index: number\n }\n\ntype SubscriberArgs = {\n location: HistoryLocation\n action: SubscriberHistoryAction\n}\n\nexport interface RouterHistory {\n location: HistoryLocation\n length: number\n subscribers: Set<(opts: SubscriberArgs) => void>\n subscribe: (cb: (opts: SubscriberArgs) => void) => () => void\n push: (path: string, state?: any, navigateOpts?: NavigateOptions) => void\n replace: (path: string, state?: any, navigateOpts?: NavigateOptions) => void\n go: (index: number, navigateOpts?: NavigateOptions) => void\n back: (navigateOpts?: NavigateOptions) => void\n forward: (navigateOpts?: NavigateOptions) => void\n canGoBack: () => boolean\n createHref: (href: string) => string\n block: (blocker: NavigationBlocker) => () => void\n flush: () => void\n destroy: () => void\n notify: (action: SubscriberHistoryAction) => void\n _ignoreSubscribers?: boolean\n}\n\nexport interface HistoryLocation extends ParsedPath {\n state: ParsedHistoryState\n}\n\nexport interface ParsedPath {\n href: string\n pathname: string\n search: string\n hash: string\n}\n\nexport interface HistoryState {}\n\nexport type ParsedHistoryState = HistoryState & {\n key?: string // TODO: Remove in v2 - use __TSR_key instead\n __TSR_key?: string\n __TSR_index: number\n}\n\ntype ShouldAllowNavigation = any\n\nexport type HistoryAction = 'PUSH' | 'REPLACE' | 'FORWARD' | 'BACK' | 'GO'\n\nexport type BlockerFnArgs = {\n currentLocation: HistoryLocation\n nextLocation: HistoryLocation\n action: HistoryAction\n}\n\nexport type BlockerFn = (\n args: BlockerFnArgs,\n) => Promise<ShouldAllowNavigation> | ShouldAllowNavigation\n\nexport type NavigationBlocker = {\n blockerFn: BlockerFn\n enableBeforeUnload?: (() => boolean) | boolean\n}\n\ntype TryNavigateArgs = {\n task: () => void\n type: 'PUSH' | 'REPLACE' | 'BACK' | 'FORWARD' | 'GO'\n navigateOpts?: NavigateOptions\n} & (\n | {\n type: 'PUSH' | 'REPLACE'\n path: string\n state: any\n }\n | {\n type: 'BACK' | 'FORWARD' | 'GO'\n }\n)\n\nconst stateIndexKey = '__TSR_index'\nconst popStateEvent = 'popstate'\nconst beforeUnloadEvent = 'beforeunload'\n\nexport function createHistory(opts: {\n getLocation: () => HistoryLocation\n getLength: () => number\n pushState: (path: string, state: any) => void\n replaceState: (path: string, state: any) => void\n go: (n: number) => void\n back: (ignoreBlocker: boolean) => void\n forward: (ignoreBlocker: boolean) => void\n createHref: (path: string) => string\n flush?: () => void\n destroy?: () => void\n onBlocked?: () => void\n getBlockers?: () => Array<NavigationBlocker>\n setBlockers?: (blockers: Array<NavigationBlocker>) => void\n // Avoid notifying on forward/back/go, used for browser history as we already get notified by the popstate event\n notifyOnIndexChange?: boolean\n}): RouterHistory {\n let location = opts.getLocation()\n const subscribers = new Set<(opts: SubscriberArgs) => void>()\n\n const notify = (action: SubscriberHistoryAction) => {\n location = opts.getLocation()\n subscribers.forEach((subscriber) => subscriber({ location, action }))\n }\n\n const handleIndexChange = (action: SubscriberHistoryAction) => {\n if (opts.notifyOnIndexChange ?? true) notify(action)\n else location = opts.getLocation()\n }\n\n const tryNavigation = async ({\n task,\n navigateOpts,\n ...actionInfo\n }: TryNavigateArgs) => {\n const ignoreBlocker = navigateOpts?.ignoreBlocker ?? false\n if (ignoreBlocker) {\n task()\n return\n }\n\n const blockers = opts.getBlockers?.() ?? []\n const isPushOrReplace =\n actionInfo.type === 'PUSH' || actionInfo.type === 'REPLACE'\n if (typeof document !== 'undefined' && blockers.length && isPushOrReplace) {\n for (const blocker of blockers) {\n const nextLocation = parseHref(actionInfo.path, actionInfo.state)\n const isBlocked = await blocker.blockerFn({\n currentLocation: location,\n nextLocation,\n action: actionInfo.type,\n })\n if (isBlocked) {\n opts.onBlocked?.()\n return\n }\n }\n }\n\n task()\n }\n\n return {\n get location() {\n return location\n },\n get length() {\n return opts.getLength()\n },\n subscribers,\n subscribe: (cb: (opts: SubscriberArgs) => void) => {\n subscribers.add(cb)\n\n return () => {\n subscribers.delete(cb)\n }\n },\n push: (path, state, navigateOpts) => {\n const currentIndex = location.state[stateIndexKey]\n state = assignKeyAndIndex(currentIndex + 1, state)\n tryNavigation({\n task: () => {\n opts.pushState(path, state)\n notify({ type: 'PUSH' })\n },\n navigateOpts,\n type: 'PUSH',\n path,\n state,\n })\n },\n replace: (path, state, navigateOpts) => {\n const currentIndex = location.state[stateIndexKey]\n state = assignKeyAndIndex(currentIndex, state)\n tryNavigation({\n task: () => {\n opts.replaceState(path, state)\n notify({ type: 'REPLACE' })\n },\n navigateOpts,\n type: 'REPLACE',\n path,\n state,\n })\n },\n go: (index, navigateOpts) => {\n tryNavigation({\n task: () => {\n opts.go(index)\n handleIndexChange({ type: 'GO', index })\n },\n navigateOpts,\n type: 'GO',\n })\n },\n back: (navigateOpts) => {\n tryNavigation({\n task: () => {\n opts.back(navigateOpts?.ignoreBlocker ?? false)\n handleIndexChange({ type: 'BACK' })\n },\n navigateOpts,\n type: 'BACK',\n })\n },\n forward: (navigateOpts) => {\n tryNavigation({\n task: () => {\n opts.forward(navigateOpts?.ignoreBlocker ?? false)\n handleIndexChange({ type: 'FORWARD' })\n },\n navigateOpts,\n type: 'FORWARD',\n })\n },\n canGoBack: () => location.state[stateIndexKey] !== 0,\n createHref: (str) => opts.createHref(str),\n block: (blocker) => {\n if (!opts.setBlockers) return () => {}\n const blockers = opts.getBlockers?.() ?? []\n opts.setBlockers([...blockers, blocker])\n\n return () => {\n const blockers = opts.getBlockers?.() ?? []\n opts.setBlockers?.(blockers.filter((b) => b !== blocker))\n }\n },\n flush: () => opts.flush?.(),\n destroy: () => opts.destroy?.(),\n notify,\n }\n}\n\nfunction assignKeyAndIndex(index: number, state: HistoryState | undefined) {\n if (!state) {\n state = {}\n }\n const key = createRandomKey()\n return {\n ...state,\n key, // TODO: Remove in v2 - use __TSR_key instead\n __TSR_key: key,\n [stateIndexKey]: index,\n } as ParsedHistoryState\n}\n\n/**\n * Creates a history object that can be used to interact with the browser's\n * navigation. This is a lightweight API wrapping the browser's native methods.\n * It is designed to work with TanStack Router, but could be used as a standalone API as well.\n * IMPORTANT: This API implements history throttling via a microtask to prevent\n * excessive calls to the history API. In some browsers, calling history.pushState or\n * history.replaceState in quick succession can cause the browser to ignore subsequent\n * calls. This API smooths out those differences and ensures that your application\n * state will *eventually* match the browser state. In most cases, this is not a problem,\n * but if you need to ensure that the browser state is up to date, you can use the\n * `history.flush` method to immediately flush all pending state changes to the browser URL.\n * @param opts\n * @param opts.getHref A function that returns the current href (path + search + hash)\n * @param opts.createHref A function that takes a path and returns a href (path + search + hash)\n * @returns A history instance\n */\nexport function createBrowserHistory(opts?: {\n parseLocation?: () => HistoryLocation\n createHref?: (path: string) => string\n window?: any\n}): RouterHistory {\n const win =\n opts?.window ??\n (typeof document !== 'undefined' ? window : (undefined as any))\n\n const originalPushState = win.history.pushState\n const originalReplaceState = win.history.replaceState\n\n let blockers: Array<NavigationBlocker> = []\n const _getBlockers = () => blockers\n const _setBlockers = (newBlockers: Array<NavigationBlocker>) =>\n (blockers = newBlockers)\n\n const createHref = opts?.createHref ?? ((path) => path)\n const parseLocation =\n opts?.parseLocation ??\n (() =>\n parseHref(\n `${win.location.pathname}${win.location.search}${win.location.hash}`,\n win.history.state,\n ))\n\n // Ensure there is always a key to start\n if (!win.history.state?.__TSR_key && !win.history.state?.key) {\n const addedKey = createRandomKey()\n win.history.replaceState(\n {\n [stateIndexKey]: 0,\n key: addedKey, // TODO: Remove in v2 - use __TSR_key instead\n __TSR_key: addedKey,\n },\n '',\n )\n }\n\n let currentLocation = parseLocation()\n let rollbackLocation: HistoryLocation | undefined\n\n let nextPopIsGo = false\n let ignoreNextPop = false\n let skipBlockerNextPop = false\n let ignoreNextBeforeUnload = false\n\n const getLocation = () => currentLocation\n\n let next:\n | undefined\n | {\n // This is the latest location that we were attempting to push/replace\n href: string\n // This is the latest state that we were attempting to push/replace\n state: any\n // This is the latest type that we were attempting to push/replace\n isPush: boolean\n }\n\n // We need to track the current scheduled update to prevent\n // multiple updates from being scheduled at the same time.\n let scheduled: Promise<void> | undefined\n\n // This function flushes the next update to the browser history\n const flush = () => {\n if (!next) {\n return\n }\n\n // We need to ignore any updates to the subscribers while we update the browser history\n history._ignoreSubscribers = true\n\n // Update the browser history\n ;(next.isPush ? win.history.pushState : win.history.replaceState)(\n next.state,\n '',\n next.href,\n )\n\n // Stop ignoring subscriber updates\n history._ignoreSubscribers = false\n\n // Reset the nextIsPush flag and clear the scheduled update\n next = undefined\n scheduled = undefined\n rollbackLocation = undefined\n }\n\n // This function queues up a call to update the browser history\n const queueHistoryAction = (\n type: 'push' | 'replace',\n destHref: string,\n state: any,\n ) => {\n const href = createHref(destHref)\n\n if (!scheduled) {\n rollbackLocation = currentLocation\n }\n\n // Update the location in memory\n currentLocation = parseHref(destHref, state)\n\n // Keep track of the next location we need to flush to the URL\n next = {\n href,\n state,\n isPush: next?.isPush || type === 'push',\n }\n\n if (!scheduled) {\n // Schedule an update to the browser history\n scheduled = Promise.resolve().then(() => flush())\n }\n }\n\n // NOTE: this function can probably be removed\n const onPushPop = (type: 'PUSH' | 'REPLACE') => {\n currentLocation = parseLocation()\n history.notify({ type })\n }\n\n const onPushPopEvent = async () => {\n if (ignoreNextPop) {\n ignoreNextPop = false\n return\n }\n\n const nextLocation = parseLocation()\n const delta =\n nextLocation.state[stateIndexKey] - currentLocation.state[stateIndexKey]\n const isForward = delta === 1\n const isBack = delta === -1\n const isGo = (!isForward && !isBack) || nextPopIsGo\n nextPopIsGo = false\n\n const action = isGo ? 'GO' : isBack ? 'BACK' : 'FORWARD'\n const notify: SubscriberHistoryAction = isGo\n ? {\n type: 'GO',\n index: delta,\n }\n : {\n type: isBack ? 'BACK' : 'FORWARD',\n }\n\n if (skipBlockerNextPop) {\n skipBlockerNextPop = false\n } else {\n const blockers = _getBlockers()\n if (typeof document !== 'undefined' && blockers.length) {\n for (const blocker of blockers) {\n const isBlocked = await blocker.blockerFn({\n currentLocation,\n nextLocation,\n action,\n })\n if (isBlocked) {\n ignoreNextPop = true\n win.history.go(1)\n history.notify(notify)\n return\n }\n }\n }\n }\n\n currentLocation = parseLocation()\n history.notify(notify)\n }\n\n const onBeforeUnload = (e: BeforeUnloadEvent) => {\n if (ignoreNextBeforeUnload) {\n ignoreNextBeforeUnload = false\n return\n }\n\n let shouldBlock = false\n\n // If one blocker has a non-disabled beforeUnload, we should block\n const blockers = _getBlockers()\n if (typeof document !== 'undefined' && blockers.length) {\n for (const blocker of blockers) {\n const shouldHaveBeforeUnload = blocker.enableBeforeUnload ?? true\n if (shouldHaveBeforeUnload === true) {\n shouldBlock = true\n break\n }\n\n if (\n typeof shouldHaveBeforeUnload === 'function' &&\n shouldHaveBeforeUnload() === true\n ) {\n shouldBlock = true\n break\n }\n }\n }\n\n if (shouldBlock) {\n e.preventDefault()\n return (e.returnValue = '')\n }\n return\n }\n\n const history = createHistory({\n getLocation,\n getLength: () => win.history.length,\n pushState: (href, state) => queueHistoryAction('push', href, state),\n replaceState: (href, state) => queueHistoryAction('replace', href, state),\n back: (ignoreBlocker) => {\n if (ignoreBlocker) skipBlockerNextPop = true\n ignoreNextBeforeUnload = true\n return win.history.back()\n },\n forward: (ignoreBlocker) => {\n if (ignoreBlocker) skipBlockerNextPop = true\n ignoreNextBeforeUnload = true\n win.history.forward()\n },\n go: (n) => {\n nextPopIsGo = true\n win.history.go(n)\n },\n createHref: (href) => createHref(href),\n flush,\n destroy: () => {\n win.history.pushState = originalPushState\n win.history.replaceState = originalReplaceState\n win.removeEventListener(beforeUnloadEvent, onBeforeUnload, {\n capture: true,\n })\n win.removeEventListener(popStateEvent, onPushPopEvent)\n },\n onBlocked: () => {\n // If a navigation is blocked, we need to rollback the location\n // that we optimistically updated in memory.\n if (rollbackLocation && currentLocation !== rollbackLocation) {\n currentLocation = rollbackLocation\n }\n },\n getBlockers: _getBlockers,\n setBlockers: _setBlockers,\n notifyOnIndexChange: false,\n })\n\n win.addEventListener(beforeUnloadEvent, onBeforeUnload, { capture: true })\n win.addEventListener(popStateEvent, onPushPopEvent)\n\n win.history.pushState = function (...args: Array<any>) {\n const res = originalPushState.apply(win.history, args as any)\n if (!history._ignoreSubscribers) onPushPop('PUSH')\n return res\n }\n\n win.history.replaceState = function (...args: Array<any>) {\n const res = originalReplaceState.apply(win.history, args as any)\n if (!history._ignoreSubscribers) onPushPop('REPLACE')\n return res\n }\n\n return history\n}\n\n/**\n * Create a hash-based history implementation.\n * Useful for static hosts or environments without server URL rewriting.\n * @link https://tanstack.com/router/latest/docs/framework/react/guide/history-types\n */\nexport function createHashHistory(opts?: { window?: any }): RouterHistory {\n const win =\n opts?.window ??\n (typeof document !== 'undefined' ? window : (undefined as any))\n return createBrowserHistory({\n window: win,\n parseLocation: () => {\n const hashSplit = win.location.hash.split('#').slice(1)\n const pathPart = hashSplit[0] ?? '/'\n const searchPart = win.location.search\n const hashEntries = hashSplit.slice(1)\n const hashPart =\n hashEntries.length === 0 ? '' : `#${hashEntries.join('#')}`\n const hashHref = `${pathPart}${searchPart}${hashPart}`\n return parseHref(hashHref, win.history.state)\n },\n createHref: (href) =>\n `${win.location.pathname}${win.location.search}#${href}`,\n })\n}\n\n/**\n * Create an in-memory history implementation.\n * Ideal for server rendering, tests, and non-DOM environments.\n * @link https://tanstack.com/router/latest/docs/framework/react/guide/history-types\n */\nexport function createMemoryHistory(\n opts: {\n initialEntries: Array<string>\n initialIndex?: number\n } = {\n initialEntries: ['/'],\n },\n): RouterHistory {\n const entries = opts.initialEntries\n let index = opts.initialIndex\n ? Math.min(Math.max(opts.initialIndex, 0), entries.length - 1)\n : entries.length - 1\n const states = entries.map((_entry, index) =>\n assignKeyAndIndex(index, undefined),\n )\n\n const getLocation = () => parseHref(entries[index]!, states[index])\n\n let blockers: Array<NavigationBlocker> = []\n const _getBlockers = () => blockers\n const _setBlockers = (newBlockers: Array<NavigationBlocker>) =>\n (blockers = newBlockers)\n\n return createHistory({\n getLocation,\n getLength: () => entries.length,\n pushState: (path, state) => {\n // Removes all subsequent entries after the current index to start a new branch\n if (index < entries.length - 1) {\n entries.splice(index + 1)\n states.splice(index + 1)\n }\n states.push(state)\n entries.push(path)\n index = Math.max(entries.length - 1, 0)\n },\n replaceState: (path, state) => {\n states[index] = state\n entries[index] = path\n },\n back: () => {\n index = Math.max(index - 1, 0)\n },\n forward: () => {\n index = Math.min(index + 1, entries.length - 1)\n },\n go: (n) => {\n index = Math.min(Math.max(index + n, 0), entries.length - 1)\n },\n createHref: (path) => path,\n getBlockers: _getBlockers,\n setBlockers: _setBlockers,\n })\n}\n\n/**\n * Sanitize a path to prevent open redirect vulnerabilities.\n * Removes control characters and collapses leading double slashes.\n */\nfunction sanitizePath(path: string): string {\n // Remove ASCII control characters (0x00-0x1F) and DEL (0x7F)\n // These include CR (\\r = 0x0D), LF (\\n = 0x0A), and other potentially dangerous characters\n // eslint-disable-next-line no-control-regex\n let sanitized = path.replace(/[\\x00-\\x1f\\x7f]/g, '')\n\n // Prevent open redirect via protocol-relative URLs (e.g. \"//evil.com\")\n // Collapse leading double slashes to a single slash\n if (sanitized.startsWith('//')) {\n sanitized = '/' + sanitized.replace(/^\\/+/, '')\n }\n\n return sanitized\n}\n\nexport function parseHref(\n href: string,\n state: ParsedHistoryState | undefined,\n): HistoryLocation {\n const sanitizedHref = sanitizePath(href)\n const hashIndex = sanitizedHref.indexOf('#')\n const searchIndex = sanitizedHref.indexOf('?')\n\n const addedKey = createRandomKey()\n\n return {\n href: sanitizedHref,\n pathname: sanitizedHref.substring(\n 0,\n hashIndex > 0\n ? searchIndex > 0\n ? Math.min(hashIndex, searchIndex)\n : hashIndex\n : searchIndex > 0\n ? searchIndex\n : sanitizedHref.length,\n ),\n hash: hashIndex > -1 ? sanitizedHref.substring(hashIndex) : '',\n search:\n searchIndex > -1\n ? sanitizedHref.slice(\n searchIndex,\n hashIndex === -1 ? undefined : hashIndex,\n )\n : '',\n state: state || { [stateIndexKey]: 0, key: addedKey, __TSR_key: addedKey },\n }\n}\n\n// Thanks co-pilot!\nfunction createRandomKey() {\n return (Math.random() + 1).toString(36).substring(7)\n}\n"],"names":["blockers","index"],"mappings":"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"} | ||
| {"version":3,"file":"index.cjs","names":[],"sources":["../../src/index.ts"],"sourcesContent":["// While the public API was clearly inspired by the \"history\" npm package,\n// This implementation attempts to be more lightweight by\n// making assumptions about the way TanStack Router works\n\nexport interface NavigateOptions {\n ignoreBlocker?: boolean\n}\n\ntype SubscriberHistoryAction =\n | {\n type: Exclude<HistoryAction, 'GO'>\n }\n | {\n type: 'GO'\n index: number\n }\n\ntype SubscriberArgs = {\n location: HistoryLocation\n action: SubscriberHistoryAction\n}\n\nexport interface RouterHistory {\n location: HistoryLocation\n length: number\n subscribers: Set<(opts: SubscriberArgs) => void>\n subscribe: (cb: (opts: SubscriberArgs) => void) => () => void\n push: (path: string, state?: any, navigateOpts?: NavigateOptions) => void\n replace: (path: string, state?: any, navigateOpts?: NavigateOptions) => void\n go: (index: number, navigateOpts?: NavigateOptions) => void\n back: (navigateOpts?: NavigateOptions) => void\n forward: (navigateOpts?: NavigateOptions) => void\n canGoBack: () => boolean\n createHref: (href: string) => string\n block: (blocker: NavigationBlocker) => () => void\n flush: () => void\n destroy: () => void\n notify: (action: SubscriberHistoryAction) => void\n _ignoreSubscribers?: boolean\n}\n\nexport interface HistoryLocation extends ParsedPath {\n state: ParsedHistoryState\n}\n\nexport interface ParsedPath {\n href: string\n pathname: string\n search: string\n hash: string\n}\n\nexport interface HistoryState {}\n\nexport type ParsedHistoryState = HistoryState & {\n key?: string // TODO: Remove in v2 - use __TSR_key instead\n __TSR_key?: string\n __TSR_index: number\n}\n\ntype ShouldAllowNavigation = any\n\nexport type HistoryAction = 'PUSH' | 'REPLACE' | 'FORWARD' | 'BACK' | 'GO'\n\nexport type BlockerFnArgs = {\n currentLocation: HistoryLocation\n nextLocation: HistoryLocation\n action: HistoryAction\n}\n\nexport type BlockerFn = (\n args: BlockerFnArgs,\n) => Promise<ShouldAllowNavigation> | ShouldAllowNavigation\n\nexport type NavigationBlocker = {\n blockerFn: BlockerFn\n enableBeforeUnload?: (() => boolean) | boolean\n}\n\ntype TryNavigateArgs = {\n task: () => void\n type: 'PUSH' | 'REPLACE' | 'BACK' | 'FORWARD' | 'GO'\n navigateOpts?: NavigateOptions\n} & (\n | {\n type: 'PUSH' | 'REPLACE'\n path: string\n state: any\n }\n | {\n type: 'BACK' | 'FORWARD' | 'GO'\n }\n)\n\nconst stateIndexKey = '__TSR_index'\nconst popStateEvent = 'popstate'\nconst beforeUnloadEvent = 'beforeunload'\n\nexport function createHistory(opts: {\n getLocation: () => HistoryLocation\n getLength: () => number\n pushState: (path: string, state: any) => void\n replaceState: (path: string, state: any) => void\n go: (n: number) => void\n back: (ignoreBlocker: boolean) => void\n forward: (ignoreBlocker: boolean) => void\n createHref: (path: string) => string\n flush?: () => void\n destroy?: () => void\n onBlocked?: () => void\n getBlockers?: () => Array<NavigationBlocker>\n setBlockers?: (blockers: Array<NavigationBlocker>) => void\n // Avoid notifying on forward/back/go, used for browser history as we already get notified by the popstate event\n notifyOnIndexChange?: boolean\n}): RouterHistory {\n let location = opts.getLocation()\n const subscribers = new Set<(opts: SubscriberArgs) => void>()\n\n const notify = (action: SubscriberHistoryAction) => {\n location = opts.getLocation()\n subscribers.forEach((subscriber) => subscriber({ location, action }))\n }\n\n const handleIndexChange = (action: SubscriberHistoryAction) => {\n if (opts.notifyOnIndexChange ?? true) notify(action)\n else location = opts.getLocation()\n }\n\n const tryNavigation = async ({\n task,\n navigateOpts,\n ...actionInfo\n }: TryNavigateArgs) => {\n const ignoreBlocker = navigateOpts?.ignoreBlocker ?? false\n if (ignoreBlocker) {\n task()\n return\n }\n\n const blockers = opts.getBlockers?.() ?? []\n const isPushOrReplace =\n actionInfo.type === 'PUSH' || actionInfo.type === 'REPLACE'\n if (typeof document !== 'undefined' && blockers.length && isPushOrReplace) {\n for (const blocker of blockers) {\n const nextLocation = parseHref(actionInfo.path, actionInfo.state)\n const isBlocked = await blocker.blockerFn({\n currentLocation: location,\n nextLocation,\n action: actionInfo.type,\n })\n if (isBlocked) {\n opts.onBlocked?.()\n return\n }\n }\n }\n\n task()\n }\n\n return {\n get location() {\n return location\n },\n get length() {\n return opts.getLength()\n },\n subscribers,\n subscribe: (cb: (opts: SubscriberArgs) => void) => {\n subscribers.add(cb)\n\n return () => {\n subscribers.delete(cb)\n }\n },\n push: (path, state, navigateOpts) => {\n const currentIndex = location.state[stateIndexKey]\n state = assignKeyAndIndex(currentIndex + 1, state)\n tryNavigation({\n task: () => {\n opts.pushState(path, state)\n notify({ type: 'PUSH' })\n },\n navigateOpts,\n type: 'PUSH',\n path,\n state,\n })\n },\n replace: (path, state, navigateOpts) => {\n const currentIndex = location.state[stateIndexKey]\n state = assignKeyAndIndex(currentIndex, state)\n tryNavigation({\n task: () => {\n opts.replaceState(path, state)\n notify({ type: 'REPLACE' })\n },\n navigateOpts,\n type: 'REPLACE',\n path,\n state,\n })\n },\n go: (index, navigateOpts) => {\n tryNavigation({\n task: () => {\n opts.go(index)\n handleIndexChange({ type: 'GO', index })\n },\n navigateOpts,\n type: 'GO',\n })\n },\n back: (navigateOpts) => {\n tryNavigation({\n task: () => {\n opts.back(navigateOpts?.ignoreBlocker ?? false)\n handleIndexChange({ type: 'BACK' })\n },\n navigateOpts,\n type: 'BACK',\n })\n },\n forward: (navigateOpts) => {\n tryNavigation({\n task: () => {\n opts.forward(navigateOpts?.ignoreBlocker ?? false)\n handleIndexChange({ type: 'FORWARD' })\n },\n navigateOpts,\n type: 'FORWARD',\n })\n },\n canGoBack: () => location.state[stateIndexKey] !== 0,\n createHref: (str) => opts.createHref(str),\n block: (blocker) => {\n if (!opts.setBlockers) return () => {}\n const blockers = opts.getBlockers?.() ?? []\n opts.setBlockers([...blockers, blocker])\n\n return () => {\n const blockers = opts.getBlockers?.() ?? []\n opts.setBlockers?.(blockers.filter((b) => b !== blocker))\n }\n },\n flush: () => opts.flush?.(),\n destroy: () => opts.destroy?.(),\n notify,\n }\n}\n\nfunction assignKeyAndIndex(index: number, state: HistoryState | undefined) {\n if (!state) {\n state = {}\n }\n const key = createRandomKey()\n return {\n ...state,\n key, // TODO: Remove in v2 - use __TSR_key instead\n __TSR_key: key,\n [stateIndexKey]: index,\n } as ParsedHistoryState\n}\n\n/**\n * Creates a history object that can be used to interact with the browser's\n * navigation. This is a lightweight API wrapping the browser's native methods.\n * It is designed to work with TanStack Router, but could be used as a standalone API as well.\n * IMPORTANT: This API implements history throttling via a microtask to prevent\n * excessive calls to the history API. In some browsers, calling history.pushState or\n * history.replaceState in quick succession can cause the browser to ignore subsequent\n * calls. This API smooths out those differences and ensures that your application\n * state will *eventually* match the browser state. In most cases, this is not a problem,\n * but if you need to ensure that the browser state is up to date, you can use the\n * `history.flush` method to immediately flush all pending state changes to the browser URL.\n * @param opts\n * @param opts.getHref A function that returns the current href (path + search + hash)\n * @param opts.createHref A function that takes a path and returns a href (path + search + hash)\n * @returns A history instance\n */\nexport function createBrowserHistory(opts?: {\n parseLocation?: () => HistoryLocation\n createHref?: (path: string) => string\n window?: any\n}): RouterHistory {\n const win =\n opts?.window ??\n (typeof document !== 'undefined' ? window : (undefined as any))\n\n const originalPushState = win.history.pushState\n const originalReplaceState = win.history.replaceState\n\n let blockers: Array<NavigationBlocker> = []\n const _getBlockers = () => blockers\n const _setBlockers = (newBlockers: Array<NavigationBlocker>) =>\n (blockers = newBlockers)\n\n const createHref = opts?.createHref ?? ((path) => path)\n const parseLocation =\n opts?.parseLocation ??\n (() =>\n parseHref(\n `${win.location.pathname}${win.location.search}${win.location.hash}`,\n win.history.state,\n ))\n\n // Ensure there is always a key to start\n if (!win.history.state?.__TSR_key && !win.history.state?.key) {\n const addedKey = createRandomKey()\n win.history.replaceState(\n {\n [stateIndexKey]: 0,\n key: addedKey, // TODO: Remove in v2 - use __TSR_key instead\n __TSR_key: addedKey,\n },\n '',\n )\n }\n\n let currentLocation = parseLocation()\n let rollbackLocation: HistoryLocation | undefined\n\n let nextPopIsGo = false\n let ignoreNextPop = false\n let skipBlockerNextPop = false\n let ignoreNextBeforeUnload = false\n\n const getLocation = () => currentLocation\n\n let next:\n | undefined\n | {\n // This is the latest location that we were attempting to push/replace\n href: string\n // This is the latest state that we were attempting to push/replace\n state: any\n // This is the latest type that we were attempting to push/replace\n isPush: boolean\n }\n\n // We need to track the current scheduled update to prevent\n // multiple updates from being scheduled at the same time.\n let scheduled: Promise<void> | undefined\n\n // This function flushes the next update to the browser history\n const flush = () => {\n if (!next) {\n return\n }\n\n // We need to ignore any updates to the subscribers while we update the browser history\n history._ignoreSubscribers = true\n\n // Update the browser history\n ;(next.isPush ? win.history.pushState : win.history.replaceState)(\n next.state,\n '',\n next.href,\n )\n\n // Stop ignoring subscriber updates\n history._ignoreSubscribers = false\n\n // Reset the nextIsPush flag and clear the scheduled update\n next = undefined\n scheduled = undefined\n rollbackLocation = undefined\n }\n\n // This function queues up a call to update the browser history\n const queueHistoryAction = (\n type: 'push' | 'replace',\n destHref: string,\n state: any,\n ) => {\n const href = createHref(destHref)\n\n if (!scheduled) {\n rollbackLocation = currentLocation\n }\n\n // Update the location in memory\n currentLocation = parseHref(destHref, state)\n\n // Keep track of the next location we need to flush to the URL\n next = {\n href,\n state,\n isPush: next?.isPush || type === 'push',\n }\n\n if (!scheduled) {\n // Schedule an update to the browser history\n scheduled = Promise.resolve().then(() => flush())\n }\n }\n\n // NOTE: this function can probably be removed\n const onPushPop = (type: 'PUSH' | 'REPLACE') => {\n currentLocation = parseLocation()\n history.notify({ type })\n }\n\n const onPushPopEvent = async () => {\n if (ignoreNextPop) {\n ignoreNextPop = false\n return\n }\n\n const nextLocation = parseLocation()\n const delta =\n nextLocation.state[stateIndexKey] - currentLocation.state[stateIndexKey]\n const isForward = delta === 1\n const isBack = delta === -1\n const isGo = (!isForward && !isBack) || nextPopIsGo\n nextPopIsGo = false\n\n const action = isGo ? 'GO' : isBack ? 'BACK' : 'FORWARD'\n const notify: SubscriberHistoryAction = isGo\n ? {\n type: 'GO',\n index: delta,\n }\n : {\n type: isBack ? 'BACK' : 'FORWARD',\n }\n\n if (skipBlockerNextPop) {\n skipBlockerNextPop = false\n } else {\n const blockers = _getBlockers()\n if (typeof document !== 'undefined' && blockers.length) {\n for (const blocker of blockers) {\n const isBlocked = await blocker.blockerFn({\n currentLocation,\n nextLocation,\n action,\n })\n if (isBlocked) {\n ignoreNextPop = true\n win.history.go(1)\n history.notify(notify)\n return\n }\n }\n }\n }\n\n currentLocation = parseLocation()\n history.notify(notify)\n }\n\n const onBeforeUnload = (e: BeforeUnloadEvent) => {\n if (ignoreNextBeforeUnload) {\n ignoreNextBeforeUnload = false\n return\n }\n\n let shouldBlock = false\n\n // If one blocker has a non-disabled beforeUnload, we should block\n const blockers = _getBlockers()\n if (typeof document !== 'undefined' && blockers.length) {\n for (const blocker of blockers) {\n const shouldHaveBeforeUnload = blocker.enableBeforeUnload ?? true\n if (shouldHaveBeforeUnload === true) {\n shouldBlock = true\n break\n }\n\n if (\n typeof shouldHaveBeforeUnload === 'function' &&\n shouldHaveBeforeUnload() === true\n ) {\n shouldBlock = true\n break\n }\n }\n }\n\n if (shouldBlock) {\n e.preventDefault()\n return (e.returnValue = '')\n }\n return\n }\n\n const history = createHistory({\n getLocation,\n getLength: () => win.history.length,\n pushState: (href, state) => queueHistoryAction('push', href, state),\n replaceState: (href, state) => queueHistoryAction('replace', href, state),\n back: (ignoreBlocker) => {\n if (ignoreBlocker) skipBlockerNextPop = true\n ignoreNextBeforeUnload = true\n return win.history.back()\n },\n forward: (ignoreBlocker) => {\n if (ignoreBlocker) skipBlockerNextPop = true\n ignoreNextBeforeUnload = true\n win.history.forward()\n },\n go: (n) => {\n nextPopIsGo = true\n win.history.go(n)\n },\n createHref: (href) => createHref(href),\n flush,\n destroy: () => {\n win.history.pushState = originalPushState\n win.history.replaceState = originalReplaceState\n win.removeEventListener(beforeUnloadEvent, onBeforeUnload, {\n capture: true,\n })\n win.removeEventListener(popStateEvent, onPushPopEvent)\n },\n onBlocked: () => {\n // If a navigation is blocked, we need to rollback the location\n // that we optimistically updated in memory.\n if (rollbackLocation && currentLocation !== rollbackLocation) {\n currentLocation = rollbackLocation\n }\n },\n getBlockers: _getBlockers,\n setBlockers: _setBlockers,\n notifyOnIndexChange: false,\n })\n\n win.addEventListener(beforeUnloadEvent, onBeforeUnload, { capture: true })\n win.addEventListener(popStateEvent, onPushPopEvent)\n\n win.history.pushState = function (...args: Array<any>) {\n const res = originalPushState.apply(win.history, args as any)\n if (!history._ignoreSubscribers) onPushPop('PUSH')\n return res\n }\n\n win.history.replaceState = function (...args: Array<any>) {\n const res = originalReplaceState.apply(win.history, args as any)\n if (!history._ignoreSubscribers) onPushPop('REPLACE')\n return res\n }\n\n return history\n}\n\n/**\n * Create a hash-based history implementation.\n * Useful for static hosts or environments without server URL rewriting.\n * @link https://tanstack.com/router/latest/docs/framework/react/guide/history-types\n */\nexport function createHashHistory(opts?: { window?: any }): RouterHistory {\n const win =\n opts?.window ??\n (typeof document !== 'undefined' ? window : (undefined as any))\n return createBrowserHistory({\n window: win,\n parseLocation: () => {\n const hashSplit = win.location.hash.split('#').slice(1)\n const pathPart = hashSplit[0] ?? '/'\n const searchPart = win.location.search\n const hashEntries = hashSplit.slice(1)\n const hashPart =\n hashEntries.length === 0 ? '' : `#${hashEntries.join('#')}`\n const hashHref = `${pathPart}${searchPart}${hashPart}`\n return parseHref(hashHref, win.history.state)\n },\n createHref: (href) =>\n `${win.location.pathname}${win.location.search}#${href}`,\n })\n}\n\n/**\n * Create an in-memory history implementation.\n * Ideal for server rendering, tests, and non-DOM environments.\n * @link https://tanstack.com/router/latest/docs/framework/react/guide/history-types\n */\nexport function createMemoryHistory(\n opts: {\n initialEntries: Array<string>\n initialIndex?: number\n } = {\n initialEntries: ['/'],\n },\n): RouterHistory {\n const entries = opts.initialEntries\n let index = opts.initialIndex\n ? Math.min(Math.max(opts.initialIndex, 0), entries.length - 1)\n : entries.length - 1\n const states = entries.map((_entry, index) =>\n assignKeyAndIndex(index, undefined),\n )\n\n const getLocation = () => parseHref(entries[index]!, states[index])\n\n let blockers: Array<NavigationBlocker> = []\n const _getBlockers = () => blockers\n const _setBlockers = (newBlockers: Array<NavigationBlocker>) =>\n (blockers = newBlockers)\n\n return createHistory({\n getLocation,\n getLength: () => entries.length,\n pushState: (path, state) => {\n // Removes all subsequent entries after the current index to start a new branch\n if (index < entries.length - 1) {\n entries.splice(index + 1)\n states.splice(index + 1)\n }\n states.push(state)\n entries.push(path)\n index = Math.max(entries.length - 1, 0)\n },\n replaceState: (path, state) => {\n states[index] = state\n entries[index] = path\n },\n back: () => {\n index = Math.max(index - 1, 0)\n },\n forward: () => {\n index = Math.min(index + 1, entries.length - 1)\n },\n go: (n) => {\n index = Math.min(Math.max(index + n, 0), entries.length - 1)\n },\n createHref: (path) => path,\n getBlockers: _getBlockers,\n setBlockers: _setBlockers,\n })\n}\n\n/**\n * Sanitize a path to prevent open redirect vulnerabilities.\n * Removes control characters and collapses leading double slashes.\n */\nfunction sanitizePath(path: string): string {\n // Remove ASCII control characters (0x00-0x1F) and DEL (0x7F)\n // These include CR (\\r = 0x0D), LF (\\n = 0x0A), and other potentially dangerous characters\n // eslint-disable-next-line no-control-regex\n let sanitized = path.replace(/[\\x00-\\x1f\\x7f]/g, '')\n\n // Prevent open redirect via protocol-relative URLs (e.g. \"//evil.com\")\n // Collapse leading double slashes to a single slash\n if (sanitized.startsWith('//')) {\n sanitized = '/' + sanitized.replace(/^\\/+/, '')\n }\n\n return sanitized\n}\n\nexport function parseHref(\n href: string,\n state: ParsedHistoryState | undefined,\n): HistoryLocation {\n const sanitizedHref = sanitizePath(href)\n const hashIndex = sanitizedHref.indexOf('#')\n const searchIndex = sanitizedHref.indexOf('?')\n\n const addedKey = createRandomKey()\n\n return {\n href: sanitizedHref,\n pathname: sanitizedHref.substring(\n 0,\n hashIndex > 0\n ? searchIndex > 0\n ? Math.min(hashIndex, searchIndex)\n : hashIndex\n : searchIndex > 0\n ? searchIndex\n : sanitizedHref.length,\n ),\n hash: hashIndex > -1 ? sanitizedHref.substring(hashIndex) : '',\n search:\n searchIndex > -1\n ? sanitizedHref.slice(\n searchIndex,\n hashIndex === -1 ? undefined : hashIndex,\n )\n : '',\n state: state || { [stateIndexKey]: 0, key: addedKey, __TSR_key: addedKey },\n }\n}\n\n// Thanks co-pilot!\nfunction createRandomKey() {\n return (Math.random() + 1).toString(36).substring(7)\n}\n"],"mappings":"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"} |
+391
-410
@@ -1,425 +0,406 @@ | ||
| const stateIndexKey = "__TSR_index"; | ||
| const popStateEvent = "popstate"; | ||
| const beforeUnloadEvent = "beforeunload"; | ||
| //#region src/index.ts | ||
| 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 | ||
| }) => { | ||
| const ignoreBlocker = navigateOpts?.ignoreBlocker ?? false; | ||
| if (ignoreBlocker) { | ||
| 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); | ||
| const isBlocked = await blocker.blockerFn({ | ||
| currentLocation: location, | ||
| nextLocation, | ||
| action: actionInfo.type | ||
| }); | ||
| if (isBlocked) { | ||
| 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 blockers2 = opts.getBlockers?.() ?? []; | ||
| opts.setBlockers?.(blockers2.filter((b) => b !== blocker)); | ||
| }; | ||
| }, | ||
| flush: () => opts.flush?.(), | ||
| destroy: () => opts.destroy?.(), | ||
| notify | ||
| }; | ||
| 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, | ||
| // TODO: Remove in v2 - use __TSR_key instead | ||
| __TSR_key: key, | ||
| [stateIndexKey]: index | ||
| }; | ||
| 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, | ||
| // TODO: Remove in v2 - use __TSR_key instead | ||
| __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 blockers2 = _getBlockers(); | ||
| if (typeof document !== "undefined" && blockers2.length) { | ||
| for (const blocker of blockers2) { | ||
| const isBlocked = await blocker.blockerFn({ | ||
| currentLocation, | ||
| nextLocation, | ||
| action | ||
| }); | ||
| if (isBlocked) { | ||
| 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 blockers2 = _getBlockers(); | ||
| if (typeof document !== "undefined" && blockers2.length) { | ||
| for (const blocker of blockers2) { | ||
| 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 = ""; | ||
| } | ||
| return; | ||
| }; | ||
| 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; | ||
| 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 a hash-based history implementation. | ||
| * Useful for static hosts or environments without server URL rewriting. | ||
| * @link https://tanstack.com/router/latest/docs/framework/react/guide/history-types | ||
| */ | ||
| function createHashHistory(opts) { | ||
| const win = opts?.window ?? (typeof document !== "undefined" ? window : void 0); | ||
| return createBrowserHistory({ | ||
| window: win, | ||
| parseLocation: () => { | ||
| const hashSplit = win.location.hash.split("#").slice(1); | ||
| const pathPart = hashSplit[0] ?? "/"; | ||
| const searchPart = win.location.search; | ||
| const hashEntries = hashSplit.slice(1); | ||
| const hashPart = hashEntries.length === 0 ? "" : `#${hashEntries.join("#")}`; | ||
| const hashHref = `${pathPart}${searchPart}${hashPart}`; | ||
| return parseHref(hashHref, win.history.state); | ||
| }, | ||
| createHref: (href) => `${win.location.pathname}${win.location.search}#${href}` | ||
| }); | ||
| const win = opts?.window ?? (typeof document !== "undefined" ? window : void 0); | ||
| return createBrowserHistory({ | ||
| window: win, | ||
| parseLocation: () => { | ||
| const hashSplit = win.location.hash.split("#").slice(1); | ||
| const pathPart = hashSplit[0] ?? "/"; | ||
| const searchPart = win.location.search; | ||
| const hashEntries = hashSplit.slice(1); | ||
| return parseHref(`${pathPart}${searchPart}${hashEntries.length === 0 ? "" : `#${hashEntries.join("#")}`}`, win.history.state); | ||
| }, | ||
| createHref: (href) => `${win.location.pathname}${win.location.search}#${href}` | ||
| }); | ||
| } | ||
| 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, index2) => assignKeyAndIndex(index2, 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 | ||
| }); | ||
| /** | ||
| * 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; | ||
| 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 } | ||
| }; | ||
| 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); | ||
| return (Math.random() + 1).toString(36).substring(7); | ||
| } | ||
| export { | ||
| createBrowserHistory, | ||
| createHashHistory, | ||
| createHistory, | ||
| createMemoryHistory, | ||
| parseHref | ||
| }; | ||
| //# sourceMappingURL=index.js.map | ||
| //#endregion | ||
| export { createBrowserHistory, createHashHistory, createHistory, createMemoryHistory, parseHref }; | ||
| //# sourceMappingURL=index.js.map |
@@ -1,1 +0,1 @@ | ||
| {"version":3,"file":"index.js","sources":["../../src/index.ts"],"sourcesContent":["// While the public API was clearly inspired by the \"history\" npm package,\n// This implementation attempts to be more lightweight by\n// making assumptions about the way TanStack Router works\n\nexport interface NavigateOptions {\n ignoreBlocker?: boolean\n}\n\ntype SubscriberHistoryAction =\n | {\n type: Exclude<HistoryAction, 'GO'>\n }\n | {\n type: 'GO'\n index: number\n }\n\ntype SubscriberArgs = {\n location: HistoryLocation\n action: SubscriberHistoryAction\n}\n\nexport interface RouterHistory {\n location: HistoryLocation\n length: number\n subscribers: Set<(opts: SubscriberArgs) => void>\n subscribe: (cb: (opts: SubscriberArgs) => void) => () => void\n push: (path: string, state?: any, navigateOpts?: NavigateOptions) => void\n replace: (path: string, state?: any, navigateOpts?: NavigateOptions) => void\n go: (index: number, navigateOpts?: NavigateOptions) => void\n back: (navigateOpts?: NavigateOptions) => void\n forward: (navigateOpts?: NavigateOptions) => void\n canGoBack: () => boolean\n createHref: (href: string) => string\n block: (blocker: NavigationBlocker) => () => void\n flush: () => void\n destroy: () => void\n notify: (action: SubscriberHistoryAction) => void\n _ignoreSubscribers?: boolean\n}\n\nexport interface HistoryLocation extends ParsedPath {\n state: ParsedHistoryState\n}\n\nexport interface ParsedPath {\n href: string\n pathname: string\n search: string\n hash: string\n}\n\nexport interface HistoryState {}\n\nexport type ParsedHistoryState = HistoryState & {\n key?: string // TODO: Remove in v2 - use __TSR_key instead\n __TSR_key?: string\n __TSR_index: number\n}\n\ntype ShouldAllowNavigation = any\n\nexport type HistoryAction = 'PUSH' | 'REPLACE' | 'FORWARD' | 'BACK' | 'GO'\n\nexport type BlockerFnArgs = {\n currentLocation: HistoryLocation\n nextLocation: HistoryLocation\n action: HistoryAction\n}\n\nexport type BlockerFn = (\n args: BlockerFnArgs,\n) => Promise<ShouldAllowNavigation> | ShouldAllowNavigation\n\nexport type NavigationBlocker = {\n blockerFn: BlockerFn\n enableBeforeUnload?: (() => boolean) | boolean\n}\n\ntype TryNavigateArgs = {\n task: () => void\n type: 'PUSH' | 'REPLACE' | 'BACK' | 'FORWARD' | 'GO'\n navigateOpts?: NavigateOptions\n} & (\n | {\n type: 'PUSH' | 'REPLACE'\n path: string\n state: any\n }\n | {\n type: 'BACK' | 'FORWARD' | 'GO'\n }\n)\n\nconst stateIndexKey = '__TSR_index'\nconst popStateEvent = 'popstate'\nconst beforeUnloadEvent = 'beforeunload'\n\nexport function createHistory(opts: {\n getLocation: () => HistoryLocation\n getLength: () => number\n pushState: (path: string, state: any) => void\n replaceState: (path: string, state: any) => void\n go: (n: number) => void\n back: (ignoreBlocker: boolean) => void\n forward: (ignoreBlocker: boolean) => void\n createHref: (path: string) => string\n flush?: () => void\n destroy?: () => void\n onBlocked?: () => void\n getBlockers?: () => Array<NavigationBlocker>\n setBlockers?: (blockers: Array<NavigationBlocker>) => void\n // Avoid notifying on forward/back/go, used for browser history as we already get notified by the popstate event\n notifyOnIndexChange?: boolean\n}): RouterHistory {\n let location = opts.getLocation()\n const subscribers = new Set<(opts: SubscriberArgs) => void>()\n\n const notify = (action: SubscriberHistoryAction) => {\n location = opts.getLocation()\n subscribers.forEach((subscriber) => subscriber({ location, action }))\n }\n\n const handleIndexChange = (action: SubscriberHistoryAction) => {\n if (opts.notifyOnIndexChange ?? true) notify(action)\n else location = opts.getLocation()\n }\n\n const tryNavigation = async ({\n task,\n navigateOpts,\n ...actionInfo\n }: TryNavigateArgs) => {\n const ignoreBlocker = navigateOpts?.ignoreBlocker ?? false\n if (ignoreBlocker) {\n task()\n return\n }\n\n const blockers = opts.getBlockers?.() ?? []\n const isPushOrReplace =\n actionInfo.type === 'PUSH' || actionInfo.type === 'REPLACE'\n if (typeof document !== 'undefined' && blockers.length && isPushOrReplace) {\n for (const blocker of blockers) {\n const nextLocation = parseHref(actionInfo.path, actionInfo.state)\n const isBlocked = await blocker.blockerFn({\n currentLocation: location,\n nextLocation,\n action: actionInfo.type,\n })\n if (isBlocked) {\n opts.onBlocked?.()\n return\n }\n }\n }\n\n task()\n }\n\n return {\n get location() {\n return location\n },\n get length() {\n return opts.getLength()\n },\n subscribers,\n subscribe: (cb: (opts: SubscriberArgs) => void) => {\n subscribers.add(cb)\n\n return () => {\n subscribers.delete(cb)\n }\n },\n push: (path, state, navigateOpts) => {\n const currentIndex = location.state[stateIndexKey]\n state = assignKeyAndIndex(currentIndex + 1, state)\n tryNavigation({\n task: () => {\n opts.pushState(path, state)\n notify({ type: 'PUSH' })\n },\n navigateOpts,\n type: 'PUSH',\n path,\n state,\n })\n },\n replace: (path, state, navigateOpts) => {\n const currentIndex = location.state[stateIndexKey]\n state = assignKeyAndIndex(currentIndex, state)\n tryNavigation({\n task: () => {\n opts.replaceState(path, state)\n notify({ type: 'REPLACE' })\n },\n navigateOpts,\n type: 'REPLACE',\n path,\n state,\n })\n },\n go: (index, navigateOpts) => {\n tryNavigation({\n task: () => {\n opts.go(index)\n handleIndexChange({ type: 'GO', index })\n },\n navigateOpts,\n type: 'GO',\n })\n },\n back: (navigateOpts) => {\n tryNavigation({\n task: () => {\n opts.back(navigateOpts?.ignoreBlocker ?? false)\n handleIndexChange({ type: 'BACK' })\n },\n navigateOpts,\n type: 'BACK',\n })\n },\n forward: (navigateOpts) => {\n tryNavigation({\n task: () => {\n opts.forward(navigateOpts?.ignoreBlocker ?? false)\n handleIndexChange({ type: 'FORWARD' })\n },\n navigateOpts,\n type: 'FORWARD',\n })\n },\n canGoBack: () => location.state[stateIndexKey] !== 0,\n createHref: (str) => opts.createHref(str),\n block: (blocker) => {\n if (!opts.setBlockers) return () => {}\n const blockers = opts.getBlockers?.() ?? []\n opts.setBlockers([...blockers, blocker])\n\n return () => {\n const blockers = opts.getBlockers?.() ?? []\n opts.setBlockers?.(blockers.filter((b) => b !== blocker))\n }\n },\n flush: () => opts.flush?.(),\n destroy: () => opts.destroy?.(),\n notify,\n }\n}\n\nfunction assignKeyAndIndex(index: number, state: HistoryState | undefined) {\n if (!state) {\n state = {}\n }\n const key = createRandomKey()\n return {\n ...state,\n key, // TODO: Remove in v2 - use __TSR_key instead\n __TSR_key: key,\n [stateIndexKey]: index,\n } as ParsedHistoryState\n}\n\n/**\n * Creates a history object that can be used to interact with the browser's\n * navigation. This is a lightweight API wrapping the browser's native methods.\n * It is designed to work with TanStack Router, but could be used as a standalone API as well.\n * IMPORTANT: This API implements history throttling via a microtask to prevent\n * excessive calls to the history API. In some browsers, calling history.pushState or\n * history.replaceState in quick succession can cause the browser to ignore subsequent\n * calls. This API smooths out those differences and ensures that your application\n * state will *eventually* match the browser state. In most cases, this is not a problem,\n * but if you need to ensure that the browser state is up to date, you can use the\n * `history.flush` method to immediately flush all pending state changes to the browser URL.\n * @param opts\n * @param opts.getHref A function that returns the current href (path + search + hash)\n * @param opts.createHref A function that takes a path and returns a href (path + search + hash)\n * @returns A history instance\n */\nexport function createBrowserHistory(opts?: {\n parseLocation?: () => HistoryLocation\n createHref?: (path: string) => string\n window?: any\n}): RouterHistory {\n const win =\n opts?.window ??\n (typeof document !== 'undefined' ? window : (undefined as any))\n\n const originalPushState = win.history.pushState\n const originalReplaceState = win.history.replaceState\n\n let blockers: Array<NavigationBlocker> = []\n const _getBlockers = () => blockers\n const _setBlockers = (newBlockers: Array<NavigationBlocker>) =>\n (blockers = newBlockers)\n\n const createHref = opts?.createHref ?? ((path) => path)\n const parseLocation =\n opts?.parseLocation ??\n (() =>\n parseHref(\n `${win.location.pathname}${win.location.search}${win.location.hash}`,\n win.history.state,\n ))\n\n // Ensure there is always a key to start\n if (!win.history.state?.__TSR_key && !win.history.state?.key) {\n const addedKey = createRandomKey()\n win.history.replaceState(\n {\n [stateIndexKey]: 0,\n key: addedKey, // TODO: Remove in v2 - use __TSR_key instead\n __TSR_key: addedKey,\n },\n '',\n )\n }\n\n let currentLocation = parseLocation()\n let rollbackLocation: HistoryLocation | undefined\n\n let nextPopIsGo = false\n let ignoreNextPop = false\n let skipBlockerNextPop = false\n let ignoreNextBeforeUnload = false\n\n const getLocation = () => currentLocation\n\n let next:\n | undefined\n | {\n // This is the latest location that we were attempting to push/replace\n href: string\n // This is the latest state that we were attempting to push/replace\n state: any\n // This is the latest type that we were attempting to push/replace\n isPush: boolean\n }\n\n // We need to track the current scheduled update to prevent\n // multiple updates from being scheduled at the same time.\n let scheduled: Promise<void> | undefined\n\n // This function flushes the next update to the browser history\n const flush = () => {\n if (!next) {\n return\n }\n\n // We need to ignore any updates to the subscribers while we update the browser history\n history._ignoreSubscribers = true\n\n // Update the browser history\n ;(next.isPush ? win.history.pushState : win.history.replaceState)(\n next.state,\n '',\n next.href,\n )\n\n // Stop ignoring subscriber updates\n history._ignoreSubscribers = false\n\n // Reset the nextIsPush flag and clear the scheduled update\n next = undefined\n scheduled = undefined\n rollbackLocation = undefined\n }\n\n // This function queues up a call to update the browser history\n const queueHistoryAction = (\n type: 'push' | 'replace',\n destHref: string,\n state: any,\n ) => {\n const href = createHref(destHref)\n\n if (!scheduled) {\n rollbackLocation = currentLocation\n }\n\n // Update the location in memory\n currentLocation = parseHref(destHref, state)\n\n // Keep track of the next location we need to flush to the URL\n next = {\n href,\n state,\n isPush: next?.isPush || type === 'push',\n }\n\n if (!scheduled) {\n // Schedule an update to the browser history\n scheduled = Promise.resolve().then(() => flush())\n }\n }\n\n // NOTE: this function can probably be removed\n const onPushPop = (type: 'PUSH' | 'REPLACE') => {\n currentLocation = parseLocation()\n history.notify({ type })\n }\n\n const onPushPopEvent = async () => {\n if (ignoreNextPop) {\n ignoreNextPop = false\n return\n }\n\n const nextLocation = parseLocation()\n const delta =\n nextLocation.state[stateIndexKey] - currentLocation.state[stateIndexKey]\n const isForward = delta === 1\n const isBack = delta === -1\n const isGo = (!isForward && !isBack) || nextPopIsGo\n nextPopIsGo = false\n\n const action = isGo ? 'GO' : isBack ? 'BACK' : 'FORWARD'\n const notify: SubscriberHistoryAction = isGo\n ? {\n type: 'GO',\n index: delta,\n }\n : {\n type: isBack ? 'BACK' : 'FORWARD',\n }\n\n if (skipBlockerNextPop) {\n skipBlockerNextPop = false\n } else {\n const blockers = _getBlockers()\n if (typeof document !== 'undefined' && blockers.length) {\n for (const blocker of blockers) {\n const isBlocked = await blocker.blockerFn({\n currentLocation,\n nextLocation,\n action,\n })\n if (isBlocked) {\n ignoreNextPop = true\n win.history.go(1)\n history.notify(notify)\n return\n }\n }\n }\n }\n\n currentLocation = parseLocation()\n history.notify(notify)\n }\n\n const onBeforeUnload = (e: BeforeUnloadEvent) => {\n if (ignoreNextBeforeUnload) {\n ignoreNextBeforeUnload = false\n return\n }\n\n let shouldBlock = false\n\n // If one blocker has a non-disabled beforeUnload, we should block\n const blockers = _getBlockers()\n if (typeof document !== 'undefined' && blockers.length) {\n for (const blocker of blockers) {\n const shouldHaveBeforeUnload = blocker.enableBeforeUnload ?? true\n if (shouldHaveBeforeUnload === true) {\n shouldBlock = true\n break\n }\n\n if (\n typeof shouldHaveBeforeUnload === 'function' &&\n shouldHaveBeforeUnload() === true\n ) {\n shouldBlock = true\n break\n }\n }\n }\n\n if (shouldBlock) {\n e.preventDefault()\n return (e.returnValue = '')\n }\n return\n }\n\n const history = createHistory({\n getLocation,\n getLength: () => win.history.length,\n pushState: (href, state) => queueHistoryAction('push', href, state),\n replaceState: (href, state) => queueHistoryAction('replace', href, state),\n back: (ignoreBlocker) => {\n if (ignoreBlocker) skipBlockerNextPop = true\n ignoreNextBeforeUnload = true\n return win.history.back()\n },\n forward: (ignoreBlocker) => {\n if (ignoreBlocker) skipBlockerNextPop = true\n ignoreNextBeforeUnload = true\n win.history.forward()\n },\n go: (n) => {\n nextPopIsGo = true\n win.history.go(n)\n },\n createHref: (href) => createHref(href),\n flush,\n destroy: () => {\n win.history.pushState = originalPushState\n win.history.replaceState = originalReplaceState\n win.removeEventListener(beforeUnloadEvent, onBeforeUnload, {\n capture: true,\n })\n win.removeEventListener(popStateEvent, onPushPopEvent)\n },\n onBlocked: () => {\n // If a navigation is blocked, we need to rollback the location\n // that we optimistically updated in memory.\n if (rollbackLocation && currentLocation !== rollbackLocation) {\n currentLocation = rollbackLocation\n }\n },\n getBlockers: _getBlockers,\n setBlockers: _setBlockers,\n notifyOnIndexChange: false,\n })\n\n win.addEventListener(beforeUnloadEvent, onBeforeUnload, { capture: true })\n win.addEventListener(popStateEvent, onPushPopEvent)\n\n win.history.pushState = function (...args: Array<any>) {\n const res = originalPushState.apply(win.history, args as any)\n if (!history._ignoreSubscribers) onPushPop('PUSH')\n return res\n }\n\n win.history.replaceState = function (...args: Array<any>) {\n const res = originalReplaceState.apply(win.history, args as any)\n if (!history._ignoreSubscribers) onPushPop('REPLACE')\n return res\n }\n\n return history\n}\n\n/**\n * Create a hash-based history implementation.\n * Useful for static hosts or environments without server URL rewriting.\n * @link https://tanstack.com/router/latest/docs/framework/react/guide/history-types\n */\nexport function createHashHistory(opts?: { window?: any }): RouterHistory {\n const win =\n opts?.window ??\n (typeof document !== 'undefined' ? window : (undefined as any))\n return createBrowserHistory({\n window: win,\n parseLocation: () => {\n const hashSplit = win.location.hash.split('#').slice(1)\n const pathPart = hashSplit[0] ?? '/'\n const searchPart = win.location.search\n const hashEntries = hashSplit.slice(1)\n const hashPart =\n hashEntries.length === 0 ? '' : `#${hashEntries.join('#')}`\n const hashHref = `${pathPart}${searchPart}${hashPart}`\n return parseHref(hashHref, win.history.state)\n },\n createHref: (href) =>\n `${win.location.pathname}${win.location.search}#${href}`,\n })\n}\n\n/**\n * Create an in-memory history implementation.\n * Ideal for server rendering, tests, and non-DOM environments.\n * @link https://tanstack.com/router/latest/docs/framework/react/guide/history-types\n */\nexport function createMemoryHistory(\n opts: {\n initialEntries: Array<string>\n initialIndex?: number\n } = {\n initialEntries: ['/'],\n },\n): RouterHistory {\n const entries = opts.initialEntries\n let index = opts.initialIndex\n ? Math.min(Math.max(opts.initialIndex, 0), entries.length - 1)\n : entries.length - 1\n const states = entries.map((_entry, index) =>\n assignKeyAndIndex(index, undefined),\n )\n\n const getLocation = () => parseHref(entries[index]!, states[index])\n\n let blockers: Array<NavigationBlocker> = []\n const _getBlockers = () => blockers\n const _setBlockers = (newBlockers: Array<NavigationBlocker>) =>\n (blockers = newBlockers)\n\n return createHistory({\n getLocation,\n getLength: () => entries.length,\n pushState: (path, state) => {\n // Removes all subsequent entries after the current index to start a new branch\n if (index < entries.length - 1) {\n entries.splice(index + 1)\n states.splice(index + 1)\n }\n states.push(state)\n entries.push(path)\n index = Math.max(entries.length - 1, 0)\n },\n replaceState: (path, state) => {\n states[index] = state\n entries[index] = path\n },\n back: () => {\n index = Math.max(index - 1, 0)\n },\n forward: () => {\n index = Math.min(index + 1, entries.length - 1)\n },\n go: (n) => {\n index = Math.min(Math.max(index + n, 0), entries.length - 1)\n },\n createHref: (path) => path,\n getBlockers: _getBlockers,\n setBlockers: _setBlockers,\n })\n}\n\n/**\n * Sanitize a path to prevent open redirect vulnerabilities.\n * Removes control characters and collapses leading double slashes.\n */\nfunction sanitizePath(path: string): string {\n // Remove ASCII control characters (0x00-0x1F) and DEL (0x7F)\n // These include CR (\\r = 0x0D), LF (\\n = 0x0A), and other potentially dangerous characters\n // eslint-disable-next-line no-control-regex\n let sanitized = path.replace(/[\\x00-\\x1f\\x7f]/g, '')\n\n // Prevent open redirect via protocol-relative URLs (e.g. \"//evil.com\")\n // Collapse leading double slashes to a single slash\n if (sanitized.startsWith('//')) {\n sanitized = '/' + sanitized.replace(/^\\/+/, '')\n }\n\n return sanitized\n}\n\nexport function parseHref(\n href: string,\n state: ParsedHistoryState | undefined,\n): HistoryLocation {\n const sanitizedHref = sanitizePath(href)\n const hashIndex = sanitizedHref.indexOf('#')\n const searchIndex = sanitizedHref.indexOf('?')\n\n const addedKey = createRandomKey()\n\n return {\n href: sanitizedHref,\n pathname: sanitizedHref.substring(\n 0,\n hashIndex > 0\n ? searchIndex > 0\n ? Math.min(hashIndex, searchIndex)\n : hashIndex\n : searchIndex > 0\n ? searchIndex\n : sanitizedHref.length,\n ),\n hash: hashIndex > -1 ? sanitizedHref.substring(hashIndex) : '',\n search:\n searchIndex > -1\n ? sanitizedHref.slice(\n searchIndex,\n hashIndex === -1 ? undefined : hashIndex,\n )\n : '',\n state: state || { [stateIndexKey]: 0, key: addedKey, __TSR_key: addedKey },\n }\n}\n\n// Thanks co-pilot!\nfunction createRandomKey() {\n return (Math.random() + 1).toString(36).substring(7)\n}\n"],"names":["blockers","index"],"mappings":"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"} | ||
| {"version":3,"file":"index.js","names":[],"sources":["../../src/index.ts"],"sourcesContent":["// While the public API was clearly inspired by the \"history\" npm package,\n// This implementation attempts to be more lightweight by\n// making assumptions about the way TanStack Router works\n\nexport interface NavigateOptions {\n ignoreBlocker?: boolean\n}\n\ntype SubscriberHistoryAction =\n | {\n type: Exclude<HistoryAction, 'GO'>\n }\n | {\n type: 'GO'\n index: number\n }\n\ntype SubscriberArgs = {\n location: HistoryLocation\n action: SubscriberHistoryAction\n}\n\nexport interface RouterHistory {\n location: HistoryLocation\n length: number\n subscribers: Set<(opts: SubscriberArgs) => void>\n subscribe: (cb: (opts: SubscriberArgs) => void) => () => void\n push: (path: string, state?: any, navigateOpts?: NavigateOptions) => void\n replace: (path: string, state?: any, navigateOpts?: NavigateOptions) => void\n go: (index: number, navigateOpts?: NavigateOptions) => void\n back: (navigateOpts?: NavigateOptions) => void\n forward: (navigateOpts?: NavigateOptions) => void\n canGoBack: () => boolean\n createHref: (href: string) => string\n block: (blocker: NavigationBlocker) => () => void\n flush: () => void\n destroy: () => void\n notify: (action: SubscriberHistoryAction) => void\n _ignoreSubscribers?: boolean\n}\n\nexport interface HistoryLocation extends ParsedPath {\n state: ParsedHistoryState\n}\n\nexport interface ParsedPath {\n href: string\n pathname: string\n search: string\n hash: string\n}\n\nexport interface HistoryState {}\n\nexport type ParsedHistoryState = HistoryState & {\n key?: string // TODO: Remove in v2 - use __TSR_key instead\n __TSR_key?: string\n __TSR_index: number\n}\n\ntype ShouldAllowNavigation = any\n\nexport type HistoryAction = 'PUSH' | 'REPLACE' | 'FORWARD' | 'BACK' | 'GO'\n\nexport type BlockerFnArgs = {\n currentLocation: HistoryLocation\n nextLocation: HistoryLocation\n action: HistoryAction\n}\n\nexport type BlockerFn = (\n args: BlockerFnArgs,\n) => Promise<ShouldAllowNavigation> | ShouldAllowNavigation\n\nexport type NavigationBlocker = {\n blockerFn: BlockerFn\n enableBeforeUnload?: (() => boolean) | boolean\n}\n\ntype TryNavigateArgs = {\n task: () => void\n type: 'PUSH' | 'REPLACE' | 'BACK' | 'FORWARD' | 'GO'\n navigateOpts?: NavigateOptions\n} & (\n | {\n type: 'PUSH' | 'REPLACE'\n path: string\n state: any\n }\n | {\n type: 'BACK' | 'FORWARD' | 'GO'\n }\n)\n\nconst stateIndexKey = '__TSR_index'\nconst popStateEvent = 'popstate'\nconst beforeUnloadEvent = 'beforeunload'\n\nexport function createHistory(opts: {\n getLocation: () => HistoryLocation\n getLength: () => number\n pushState: (path: string, state: any) => void\n replaceState: (path: string, state: any) => void\n go: (n: number) => void\n back: (ignoreBlocker: boolean) => void\n forward: (ignoreBlocker: boolean) => void\n createHref: (path: string) => string\n flush?: () => void\n destroy?: () => void\n onBlocked?: () => void\n getBlockers?: () => Array<NavigationBlocker>\n setBlockers?: (blockers: Array<NavigationBlocker>) => void\n // Avoid notifying on forward/back/go, used for browser history as we already get notified by the popstate event\n notifyOnIndexChange?: boolean\n}): RouterHistory {\n let location = opts.getLocation()\n const subscribers = new Set<(opts: SubscriberArgs) => void>()\n\n const notify = (action: SubscriberHistoryAction) => {\n location = opts.getLocation()\n subscribers.forEach((subscriber) => subscriber({ location, action }))\n }\n\n const handleIndexChange = (action: SubscriberHistoryAction) => {\n if (opts.notifyOnIndexChange ?? true) notify(action)\n else location = opts.getLocation()\n }\n\n const tryNavigation = async ({\n task,\n navigateOpts,\n ...actionInfo\n }: TryNavigateArgs) => {\n const ignoreBlocker = navigateOpts?.ignoreBlocker ?? false\n if (ignoreBlocker) {\n task()\n return\n }\n\n const blockers = opts.getBlockers?.() ?? []\n const isPushOrReplace =\n actionInfo.type === 'PUSH' || actionInfo.type === 'REPLACE'\n if (typeof document !== 'undefined' && blockers.length && isPushOrReplace) {\n for (const blocker of blockers) {\n const nextLocation = parseHref(actionInfo.path, actionInfo.state)\n const isBlocked = await blocker.blockerFn({\n currentLocation: location,\n nextLocation,\n action: actionInfo.type,\n })\n if (isBlocked) {\n opts.onBlocked?.()\n return\n }\n }\n }\n\n task()\n }\n\n return {\n get location() {\n return location\n },\n get length() {\n return opts.getLength()\n },\n subscribers,\n subscribe: (cb: (opts: SubscriberArgs) => void) => {\n subscribers.add(cb)\n\n return () => {\n subscribers.delete(cb)\n }\n },\n push: (path, state, navigateOpts) => {\n const currentIndex = location.state[stateIndexKey]\n state = assignKeyAndIndex(currentIndex + 1, state)\n tryNavigation({\n task: () => {\n opts.pushState(path, state)\n notify({ type: 'PUSH' })\n },\n navigateOpts,\n type: 'PUSH',\n path,\n state,\n })\n },\n replace: (path, state, navigateOpts) => {\n const currentIndex = location.state[stateIndexKey]\n state = assignKeyAndIndex(currentIndex, state)\n tryNavigation({\n task: () => {\n opts.replaceState(path, state)\n notify({ type: 'REPLACE' })\n },\n navigateOpts,\n type: 'REPLACE',\n path,\n state,\n })\n },\n go: (index, navigateOpts) => {\n tryNavigation({\n task: () => {\n opts.go(index)\n handleIndexChange({ type: 'GO', index })\n },\n navigateOpts,\n type: 'GO',\n })\n },\n back: (navigateOpts) => {\n tryNavigation({\n task: () => {\n opts.back(navigateOpts?.ignoreBlocker ?? false)\n handleIndexChange({ type: 'BACK' })\n },\n navigateOpts,\n type: 'BACK',\n })\n },\n forward: (navigateOpts) => {\n tryNavigation({\n task: () => {\n opts.forward(navigateOpts?.ignoreBlocker ?? false)\n handleIndexChange({ type: 'FORWARD' })\n },\n navigateOpts,\n type: 'FORWARD',\n })\n },\n canGoBack: () => location.state[stateIndexKey] !== 0,\n createHref: (str) => opts.createHref(str),\n block: (blocker) => {\n if (!opts.setBlockers) return () => {}\n const blockers = opts.getBlockers?.() ?? []\n opts.setBlockers([...blockers, blocker])\n\n return () => {\n const blockers = opts.getBlockers?.() ?? []\n opts.setBlockers?.(blockers.filter((b) => b !== blocker))\n }\n },\n flush: () => opts.flush?.(),\n destroy: () => opts.destroy?.(),\n notify,\n }\n}\n\nfunction assignKeyAndIndex(index: number, state: HistoryState | undefined) {\n if (!state) {\n state = {}\n }\n const key = createRandomKey()\n return {\n ...state,\n key, // TODO: Remove in v2 - use __TSR_key instead\n __TSR_key: key,\n [stateIndexKey]: index,\n } as ParsedHistoryState\n}\n\n/**\n * Creates a history object that can be used to interact with the browser's\n * navigation. This is a lightweight API wrapping the browser's native methods.\n * It is designed to work with TanStack Router, but could be used as a standalone API as well.\n * IMPORTANT: This API implements history throttling via a microtask to prevent\n * excessive calls to the history API. In some browsers, calling history.pushState or\n * history.replaceState in quick succession can cause the browser to ignore subsequent\n * calls. This API smooths out those differences and ensures that your application\n * state will *eventually* match the browser state. In most cases, this is not a problem,\n * but if you need to ensure that the browser state is up to date, you can use the\n * `history.flush` method to immediately flush all pending state changes to the browser URL.\n * @param opts\n * @param opts.getHref A function that returns the current href (path + search + hash)\n * @param opts.createHref A function that takes a path and returns a href (path + search + hash)\n * @returns A history instance\n */\nexport function createBrowserHistory(opts?: {\n parseLocation?: () => HistoryLocation\n createHref?: (path: string) => string\n window?: any\n}): RouterHistory {\n const win =\n opts?.window ??\n (typeof document !== 'undefined' ? window : (undefined as any))\n\n const originalPushState = win.history.pushState\n const originalReplaceState = win.history.replaceState\n\n let blockers: Array<NavigationBlocker> = []\n const _getBlockers = () => blockers\n const _setBlockers = (newBlockers: Array<NavigationBlocker>) =>\n (blockers = newBlockers)\n\n const createHref = opts?.createHref ?? ((path) => path)\n const parseLocation =\n opts?.parseLocation ??\n (() =>\n parseHref(\n `${win.location.pathname}${win.location.search}${win.location.hash}`,\n win.history.state,\n ))\n\n // Ensure there is always a key to start\n if (!win.history.state?.__TSR_key && !win.history.state?.key) {\n const addedKey = createRandomKey()\n win.history.replaceState(\n {\n [stateIndexKey]: 0,\n key: addedKey, // TODO: Remove in v2 - use __TSR_key instead\n __TSR_key: addedKey,\n },\n '',\n )\n }\n\n let currentLocation = parseLocation()\n let rollbackLocation: HistoryLocation | undefined\n\n let nextPopIsGo = false\n let ignoreNextPop = false\n let skipBlockerNextPop = false\n let ignoreNextBeforeUnload = false\n\n const getLocation = () => currentLocation\n\n let next:\n | undefined\n | {\n // This is the latest location that we were attempting to push/replace\n href: string\n // This is the latest state that we were attempting to push/replace\n state: any\n // This is the latest type that we were attempting to push/replace\n isPush: boolean\n }\n\n // We need to track the current scheduled update to prevent\n // multiple updates from being scheduled at the same time.\n let scheduled: Promise<void> | undefined\n\n // This function flushes the next update to the browser history\n const flush = () => {\n if (!next) {\n return\n }\n\n // We need to ignore any updates to the subscribers while we update the browser history\n history._ignoreSubscribers = true\n\n // Update the browser history\n ;(next.isPush ? win.history.pushState : win.history.replaceState)(\n next.state,\n '',\n next.href,\n )\n\n // Stop ignoring subscriber updates\n history._ignoreSubscribers = false\n\n // Reset the nextIsPush flag and clear the scheduled update\n next = undefined\n scheduled = undefined\n rollbackLocation = undefined\n }\n\n // This function queues up a call to update the browser history\n const queueHistoryAction = (\n type: 'push' | 'replace',\n destHref: string,\n state: any,\n ) => {\n const href = createHref(destHref)\n\n if (!scheduled) {\n rollbackLocation = currentLocation\n }\n\n // Update the location in memory\n currentLocation = parseHref(destHref, state)\n\n // Keep track of the next location we need to flush to the URL\n next = {\n href,\n state,\n isPush: next?.isPush || type === 'push',\n }\n\n if (!scheduled) {\n // Schedule an update to the browser history\n scheduled = Promise.resolve().then(() => flush())\n }\n }\n\n // NOTE: this function can probably be removed\n const onPushPop = (type: 'PUSH' | 'REPLACE') => {\n currentLocation = parseLocation()\n history.notify({ type })\n }\n\n const onPushPopEvent = async () => {\n if (ignoreNextPop) {\n ignoreNextPop = false\n return\n }\n\n const nextLocation = parseLocation()\n const delta =\n nextLocation.state[stateIndexKey] - currentLocation.state[stateIndexKey]\n const isForward = delta === 1\n const isBack = delta === -1\n const isGo = (!isForward && !isBack) || nextPopIsGo\n nextPopIsGo = false\n\n const action = isGo ? 'GO' : isBack ? 'BACK' : 'FORWARD'\n const notify: SubscriberHistoryAction = isGo\n ? {\n type: 'GO',\n index: delta,\n }\n : {\n type: isBack ? 'BACK' : 'FORWARD',\n }\n\n if (skipBlockerNextPop) {\n skipBlockerNextPop = false\n } else {\n const blockers = _getBlockers()\n if (typeof document !== 'undefined' && blockers.length) {\n for (const blocker of blockers) {\n const isBlocked = await blocker.blockerFn({\n currentLocation,\n nextLocation,\n action,\n })\n if (isBlocked) {\n ignoreNextPop = true\n win.history.go(1)\n history.notify(notify)\n return\n }\n }\n }\n }\n\n currentLocation = parseLocation()\n history.notify(notify)\n }\n\n const onBeforeUnload = (e: BeforeUnloadEvent) => {\n if (ignoreNextBeforeUnload) {\n ignoreNextBeforeUnload = false\n return\n }\n\n let shouldBlock = false\n\n // If one blocker has a non-disabled beforeUnload, we should block\n const blockers = _getBlockers()\n if (typeof document !== 'undefined' && blockers.length) {\n for (const blocker of blockers) {\n const shouldHaveBeforeUnload = blocker.enableBeforeUnload ?? true\n if (shouldHaveBeforeUnload === true) {\n shouldBlock = true\n break\n }\n\n if (\n typeof shouldHaveBeforeUnload === 'function' &&\n shouldHaveBeforeUnload() === true\n ) {\n shouldBlock = true\n break\n }\n }\n }\n\n if (shouldBlock) {\n e.preventDefault()\n return (e.returnValue = '')\n }\n return\n }\n\n const history = createHistory({\n getLocation,\n getLength: () => win.history.length,\n pushState: (href, state) => queueHistoryAction('push', href, state),\n replaceState: (href, state) => queueHistoryAction('replace', href, state),\n back: (ignoreBlocker) => {\n if (ignoreBlocker) skipBlockerNextPop = true\n ignoreNextBeforeUnload = true\n return win.history.back()\n },\n forward: (ignoreBlocker) => {\n if (ignoreBlocker) skipBlockerNextPop = true\n ignoreNextBeforeUnload = true\n win.history.forward()\n },\n go: (n) => {\n nextPopIsGo = true\n win.history.go(n)\n },\n createHref: (href) => createHref(href),\n flush,\n destroy: () => {\n win.history.pushState = originalPushState\n win.history.replaceState = originalReplaceState\n win.removeEventListener(beforeUnloadEvent, onBeforeUnload, {\n capture: true,\n })\n win.removeEventListener(popStateEvent, onPushPopEvent)\n },\n onBlocked: () => {\n // If a navigation is blocked, we need to rollback the location\n // that we optimistically updated in memory.\n if (rollbackLocation && currentLocation !== rollbackLocation) {\n currentLocation = rollbackLocation\n }\n },\n getBlockers: _getBlockers,\n setBlockers: _setBlockers,\n notifyOnIndexChange: false,\n })\n\n win.addEventListener(beforeUnloadEvent, onBeforeUnload, { capture: true })\n win.addEventListener(popStateEvent, onPushPopEvent)\n\n win.history.pushState = function (...args: Array<any>) {\n const res = originalPushState.apply(win.history, args as any)\n if (!history._ignoreSubscribers) onPushPop('PUSH')\n return res\n }\n\n win.history.replaceState = function (...args: Array<any>) {\n const res = originalReplaceState.apply(win.history, args as any)\n if (!history._ignoreSubscribers) onPushPop('REPLACE')\n return res\n }\n\n return history\n}\n\n/**\n * Create a hash-based history implementation.\n * Useful for static hosts or environments without server URL rewriting.\n * @link https://tanstack.com/router/latest/docs/framework/react/guide/history-types\n */\nexport function createHashHistory(opts?: { window?: any }): RouterHistory {\n const win =\n opts?.window ??\n (typeof document !== 'undefined' ? window : (undefined as any))\n return createBrowserHistory({\n window: win,\n parseLocation: () => {\n const hashSplit = win.location.hash.split('#').slice(1)\n const pathPart = hashSplit[0] ?? '/'\n const searchPart = win.location.search\n const hashEntries = hashSplit.slice(1)\n const hashPart =\n hashEntries.length === 0 ? '' : `#${hashEntries.join('#')}`\n const hashHref = `${pathPart}${searchPart}${hashPart}`\n return parseHref(hashHref, win.history.state)\n },\n createHref: (href) =>\n `${win.location.pathname}${win.location.search}#${href}`,\n })\n}\n\n/**\n * Create an in-memory history implementation.\n * Ideal for server rendering, tests, and non-DOM environments.\n * @link https://tanstack.com/router/latest/docs/framework/react/guide/history-types\n */\nexport function createMemoryHistory(\n opts: {\n initialEntries: Array<string>\n initialIndex?: number\n } = {\n initialEntries: ['/'],\n },\n): RouterHistory {\n const entries = opts.initialEntries\n let index = opts.initialIndex\n ? Math.min(Math.max(opts.initialIndex, 0), entries.length - 1)\n : entries.length - 1\n const states = entries.map((_entry, index) =>\n assignKeyAndIndex(index, undefined),\n )\n\n const getLocation = () => parseHref(entries[index]!, states[index])\n\n let blockers: Array<NavigationBlocker> = []\n const _getBlockers = () => blockers\n const _setBlockers = (newBlockers: Array<NavigationBlocker>) =>\n (blockers = newBlockers)\n\n return createHistory({\n getLocation,\n getLength: () => entries.length,\n pushState: (path, state) => {\n // Removes all subsequent entries after the current index to start a new branch\n if (index < entries.length - 1) {\n entries.splice(index + 1)\n states.splice(index + 1)\n }\n states.push(state)\n entries.push(path)\n index = Math.max(entries.length - 1, 0)\n },\n replaceState: (path, state) => {\n states[index] = state\n entries[index] = path\n },\n back: () => {\n index = Math.max(index - 1, 0)\n },\n forward: () => {\n index = Math.min(index + 1, entries.length - 1)\n },\n go: (n) => {\n index = Math.min(Math.max(index + n, 0), entries.length - 1)\n },\n createHref: (path) => path,\n getBlockers: _getBlockers,\n setBlockers: _setBlockers,\n })\n}\n\n/**\n * Sanitize a path to prevent open redirect vulnerabilities.\n * Removes control characters and collapses leading double slashes.\n */\nfunction sanitizePath(path: string): string {\n // Remove ASCII control characters (0x00-0x1F) and DEL (0x7F)\n // These include CR (\\r = 0x0D), LF (\\n = 0x0A), and other potentially dangerous characters\n // eslint-disable-next-line no-control-regex\n let sanitized = path.replace(/[\\x00-\\x1f\\x7f]/g, '')\n\n // Prevent open redirect via protocol-relative URLs (e.g. \"//evil.com\")\n // Collapse leading double slashes to a single slash\n if (sanitized.startsWith('//')) {\n sanitized = '/' + sanitized.replace(/^\\/+/, '')\n }\n\n return sanitized\n}\n\nexport function parseHref(\n href: string,\n state: ParsedHistoryState | undefined,\n): HistoryLocation {\n const sanitizedHref = sanitizePath(href)\n const hashIndex = sanitizedHref.indexOf('#')\n const searchIndex = sanitizedHref.indexOf('?')\n\n const addedKey = createRandomKey()\n\n return {\n href: sanitizedHref,\n pathname: sanitizedHref.substring(\n 0,\n hashIndex > 0\n ? searchIndex > 0\n ? Math.min(hashIndex, searchIndex)\n : hashIndex\n : searchIndex > 0\n ? searchIndex\n : sanitizedHref.length,\n ),\n hash: hashIndex > -1 ? sanitizedHref.substring(hashIndex) : '',\n search:\n searchIndex > -1\n ? sanitizedHref.slice(\n searchIndex,\n hashIndex === -1 ? undefined : hashIndex,\n )\n : '',\n state: state || { [stateIndexKey]: 0, key: addedKey, __TSR_key: addedKey },\n }\n}\n\n// Thanks co-pilot!\nfunction createRandomKey() {\n return (Math.random() + 1).toString(36).substring(7)\n}\n"],"mappings":"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"} |
+1
-1
| { | ||
| "name": "@tanstack/history", | ||
| "version": "1.161.5", | ||
| "version": "1.161.6", | ||
| "description": "Modern and scalable routing for React applications", | ||
@@ -5,0 +5,0 @@ "author": "Tanner Linsley", |
113512
-2.53%1544
-2.34%