/*! * vue-router v5.0.3 * (c) 2026 Eduardo San Martin Morote * @license MIT */ import { d as isNavigationFailure, l as NavigationFailureType } from "./useApi-j1E6pMaV.js"; import { effectScope, inject, shallowRef } from "vue"; //#region src/experimental/data-loaders/createDataLoader.ts const toLazyValue = (lazy, to, from) => typeof lazy === "function" ? lazy(to, from) : lazy; //#endregion //#region src/experimental/data-loaders/symbols.ts /** * Retrieves the internal version of loaders. * @internal */ const LOADER_SET_KEY = Symbol("loaders"); /** * Retrieves the internal version of loader entries. * @internal */ const LOADER_ENTRIES_KEY = Symbol("loaderEntries"); /** * Added to the loaders returned by `defineLoader()` to identify them. * Allows to extract exported useData() from a component. * @internal */ const IS_USE_DATA_LOADER_KEY = Symbol(); /** * Symbol used to save the pending location on the router. * @internal */ const PENDING_LOCATION_KEY = Symbol(); /** * Symbol used to know there is no value staged for the loader and that commit should be skipped. * @internal */ const STAGED_NO_VALUE = Symbol(); /** * Gives access to the current app and it's `runWithContext` method. * @internal */ const APP_KEY = Symbol(); /** * Gives access to an AbortController that aborts when the navigation is canceled. * @internal */ const ABORT_CONTROLLER_KEY = Symbol(); /** * Symbol used to save the initial data on the router. * @internal */ const IS_SSR_KEY = Symbol(); /** * Symbol used to get the effect scope used for data loaders. * @internal */ const DATA_LOADERS_EFFECT_SCOPE_KEY = Symbol(); //#endregion //#region src/experimental/data-loaders/utils.ts /** * Check if a value is a `DataLoader`. * * @param loader - the object to check */ function isDataLoader(loader) { return loader && loader[IS_USE_DATA_LOADER_KEY]; } /** * @internal: data loaders authoring only. Use `getCurrentContext` instead. */ let currentContext; function getCurrentContext() { return currentContext || []; } /** * Sets the current context for data loaders. This allows for nested loaders to be aware of their parent context. * INTERNAL ONLY. * * @param context - the context to set * @internal */ function setCurrentContext(context) { currentContext = context ? context.length ? context : null : null; } /** * Restore the current context after a promise is resolved. * @param promise - promise to wrap */ function withLoaderContext(promise) { const context = currentContext; return promise.finally(() => currentContext = context); } const assign = Object.assign; /** * Track the reads of a route and its properties * @internal * @param route - route to track */ function trackRoute(route) { const [params, paramReads] = trackObjectReads(route.params); const [query, queryReads] = trackObjectReads(route.query); let hash = { v: null }; return [ { ...route, get hash() { return hash.v = route.hash; }, params, query }, paramReads, queryReads, hash ]; } /** * Track the reads of an object (that doesn't change) and add the read properties to an object * @internal * @param obj - object to track */ function trackObjectReads(obj) { const reads = {}; return [new Proxy(obj, { get(target, p, receiver) { const value = Reflect.get(target, p, receiver); reads[p] = value; return value; } }), reads]; } /** * Returns `true` if `inner` is a subset of `outer`. Used to check if a tr * * @internal * @param outer - the bigger params * @param inner - the smaller params */ function isSubsetOf(inner, outer) { for (const key in inner) { const innerValue = inner[key]; const outerValue = outer[key]; if (typeof innerValue === "string") { if (innerValue !== outerValue) return false; } else if (!innerValue || !outerValue) { if (innerValue !== outerValue) return false; } else if (!Array.isArray(outerValue) || outerValue.length !== innerValue.length || innerValue.some((value, i) => value !== outerValue[i])) return false; } return true; } //#endregion //#region src/experimental/data-loaders/navigation-guard.ts /** * Key to inject the global loading state for loaders used in `useIsDataLoading`. * @internal */ const IS_DATA_LOADING_KEY = Symbol(); /** * TODO: export functions that allow preloading outside of a navigation guard */ /** * Setups the different Navigation Guards to collect the data loaders from the route records and then to execute them. * @internal used by the `DataLoaderPlugin` * @see {@link DataLoaderPlugin} * * @param router - the router instance * @returns */ function setupLoaderGuard({ router, app, effect: scope, isSSR, errors: globalErrors = [] }) { if (router[LOADER_ENTRIES_KEY] != null) { if (process.env.NODE_ENV !== "production") console.warn("[vue-router]: Data Loader was setup twice. Make sure to setup only once."); return () => {}; } if (process.env.NODE_ENV === "development" && !isSSR) console.warn("[vue-router]: Data Loader is experimental and subject to breaking changes in the future."); router[LOADER_ENTRIES_KEY] = /* @__PURE__ */ new WeakMap(); router[APP_KEY] = app; router[DATA_LOADERS_EFFECT_SCOPE_KEY] = scope; router[IS_SSR_KEY] = !!isSSR; const isDataLoading = scope.run(() => shallowRef(false)); app.provide(IS_DATA_LOADING_KEY, isDataLoading); const removeLoaderGuard = router.beforeEach((to) => { if (router[PENDING_LOCATION_KEY]) router[PENDING_LOCATION_KEY].meta[ABORT_CONTROLLER_KEY]?.abort(); router[PENDING_LOCATION_KEY] = to; to.meta[LOADER_SET_KEY] = /* @__PURE__ */ new Set(); to.meta[ABORT_CONTROLLER_KEY] = new AbortController(); for (const record of to.matched) record.meta[LOADER_SET_KEY] ??= new Set(record.meta.loaders || []); }); const removeDataLoaderGuard = router.beforeResolve((to, from) => { for (const record of to.matched) { for (const loader of record.meta[LOADER_SET_KEY]) to.meta[LOADER_SET_KEY].add(loader); for (const componentName in record.mods) { const viewModule = record.mods[componentName]; for (const exportName in viewModule) { const exportValue = viewModule[exportName]; if (isDataLoader(exportValue)) to.meta[LOADER_SET_KEY].add(exportValue); } const component = record.components?.[componentName]; if (component && Array.isArray(component.__loaders)) { for (const loader of component.__loaders) if (isDataLoader(loader)) to.meta[LOADER_SET_KEY].add(loader); } } } const loaders = Array.from(to.meta[LOADER_SET_KEY]); const { signal } = to.meta[ABORT_CONTROLLER_KEY]; setCurrentContext([]); isDataLoading.value = true; return Promise.all(loaders.map((loader) => { const { server, lazy, errors } = loader._.options; if (!server && isSSR) return; const ret = scope.run(() => app.runWithContext(() => loader._.load(to, router, from))); return !isSSR && toLazyValue(lazy, to, from) ? void 0 : ret.catch((reason) => { if (errors === true) { if (Array.isArray(globalErrors) ? globalErrors.some((Err) => reason instanceof Err) : globalErrors(reason)) return; } else if (errors && (Array.isArray(errors) ? errors.some((Err) => reason instanceof Err) : errors(reason))) return; throw reason; }); })).then((results) => { if (process.env.NODE_ENV !== "production") { for (const result of results) if (result instanceof NavigationResult) { console.warn("[vue-router]: Returning a NavigationResult from a loader is deprecated. Use reroute() instead, which throws internally."); throw result; } } }).catch((error) => error instanceof NavigationResult ? error.value : signal.aborted && error === signal.reason ? false : Promise.reject(error)).finally(() => { setCurrentContext([]); isDataLoading.value = false; }); }); const removeAfterEach = router.afterEach((to, from, failure) => { if (failure) { to.meta[ABORT_CONTROLLER_KEY]?.abort(failure); if (isNavigationFailure(failure, NavigationFailureType.duplicated)) for (const loader of to.meta[LOADER_SET_KEY]) loader._.getEntry(router).resetPending(); } else for (const loader of to.meta[LOADER_SET_KEY]) { const { commit, lazy } = loader._.options; if (commit === "after-load") { const entry = loader._.getEntry(router); if (entry && (!toLazyValue(lazy, to, from) || !entry.isLoading.value)) entry.commit(to); } } if (router[PENDING_LOCATION_KEY] === to) router[PENDING_LOCATION_KEY] = null; }); const removeOnError = router.onError((error, to) => { to.meta[ABORT_CONTROLLER_KEY]?.abort(error); if (router[PENDING_LOCATION_KEY] === to) router[PENDING_LOCATION_KEY] = null; }); return () => { delete router[LOADER_ENTRIES_KEY]; delete router[APP_KEY]; removeLoaderGuard(); removeDataLoaderGuard(); removeAfterEach(); removeOnError(); }; } /** * Allows data loaders to change navigation. Called by {@link reroute}. * * @internal */ var NavigationResult = class { constructor(value) { this.value = value; } }; /** * Changes the navigation from within a data loader. Accepts the same values as a navigation * guard return: a route location to redirect to, or `false` to cancel the navigation. * * @example * ```ts * export const useUserData = defineBasicLoader(async (to) => { * const user = await fetchUser(to.params.id) * if (!user) { * reroute('/404') * } * return user * }) * ``` */ function reroute(to) { throw new NavigationResult(to); } /** * Data Loader plugin to add data loading support to Vue Router. * * @example * ```ts * const router = createRouter({ * routes, * history: createWebHistory(), * }) * * const app = createApp({}) * app.use(DataLoaderPlugin, { router }) * app.use(router) * ``` */ function DataLoaderPlugin(app, options) { const effect = effectScope(true); const removeGuards = setupLoaderGuard(assign({ app, effect }, options)); const { unmount } = app; app.unmount = () => { effect.stop(); removeGuards(); unmount.call(app); }; } /** * Return a ref that reflects the global loading state of all loaders within a navigation. * This state doesn't update if `refresh()` is manually called. */ function useIsDataLoading() { return inject(IS_DATA_LOADING_KEY); } //#endregion export { LOADER_SET_KEY as _, assign as a, toLazyValue as b, setCurrentContext as c, ABORT_CONTROLLER_KEY as d, APP_KEY as f, LOADER_ENTRIES_KEY as g, IS_USE_DATA_LOADER_KEY as h, useIsDataLoading as i, trackRoute as l, IS_SSR_KEY as m, NavigationResult as n, getCurrentContext as o, DATA_LOADERS_EFFECT_SCOPE_KEY as p, reroute as r, isSubsetOf as s, DataLoaderPlugin as t, withLoaderContext as u, PENDING_LOCATION_KEY as v, STAGED_NO_VALUE as y };