first commit
This commit is contained in:
+190
@@ -0,0 +1,190 @@
|
||||
/*!
|
||||
* vue-router v5.0.6
|
||||
* (c) 2026 Eduardo San Martin Morote
|
||||
* @license MIT
|
||||
*/
|
||||
import { a as DefineLoaderFn, b as trackRoute, c as UseDataLoaderResult, d as DataLoaderPlugin, f as DataLoaderPluginOptions, g as useIsDataLoading, h as reroute, i as DefineDataLoaderOptionsBase_LaxData, l as toLazyValue, m as SetupLoaderGuardOptions, n as DataLoaderEntryBase, o as UseDataLoader, p as NavigationResult$1, r as DefineDataLoaderOptionsBase_DefinedData, s as UseDataLoaderInternals, t as DataLoaderContextBase, u as ErrorDefault, v as getCurrentContext, x as withLoaderContext, y as setCurrentContext } from "../index-ZwgQvn2r.js";
|
||||
import { $ as MatcherPatternPathStatic, A as EXPERIMENTAL_RouterOptions, B as defineQueryParamParser, C as EXPERIMENTAL_RouteRecordNormalized_Group, Cn as RouteRecordRaw, D as EXPERIMENTAL_RouteRecord_Group, E as EXPERIMENTAL_RouteRecord_Base, F as createFixedResolver, H as normalizeParamParser, I as MatcherPatternQuery, J as MatcherPattern, K as EmptyParams, L as MatcherPatternQueryParam, M as EXPERIMENTAL_Router_Base, N as experimental_createRouter, Nt as RouteLocationNormalizedLoaded, O as EXPERIMENTAL_RouteRecord_Matchable, P as normalizeRouteRecord, Q as MatcherPatternPathDynamic_ParamOptions, R as defineParamParser, S as EXPERIMENTAL_RouteRecordNormalized, T as EXPERIMENTAL_RouteRecordRaw, U as PARAM_PARSER_INT, Un as TypesConfig, V as ExtractParamParserType, W as PARAM_PARSER_BOOL, X as MatcherPatternPath, Y as MatcherPatternHash, Z as MatcherPatternPathDynamic, et as MatcherQueryParams, j as EXPERIMENTAL_RouterOptions_Base, k as EXPERIMENTAL_Router, nt as ParamParser, q as MatcherParamsFormatted, rn as RouteMap, tt as MatcherQueryParamsValue, w as EXPERIMENTAL_RouteRecordNormalized_Matchable, z as definePathParamParser } from "../index-D_VEAp3P.js";
|
||||
|
||||
//#region src/experimental/route-resolver/matchers/errors.d.ts
|
||||
/**
|
||||
* Error throw when a matcher matches by regex but validation fails.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
declare class MatchMiss extends Error {
|
||||
name: string;
|
||||
}
|
||||
/**
|
||||
* Helper to throw a {@link MatchMiss} error.
|
||||
* @param args - Arguments to pass to the `MatchMiss` constructor.
|
||||
*
|
||||
* @example
|
||||
* ```ts
|
||||
* miss()
|
||||
* // in a number param matcher
|
||||
* miss('Number must be finite')
|
||||
* ```
|
||||
*/
|
||||
declare const miss: (...args: ConstructorParameters<typeof MatchMiss>) => never;
|
||||
//#endregion
|
||||
//#region src/experimental/runtime.d.ts
|
||||
/**
|
||||
* Helper to define page properties with file-based routing.
|
||||
* **Doesn't do anything**, used for types only.
|
||||
*
|
||||
* @param route - route information to be added to this page
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
declare const definePage: (route: DefinePage) => DefinePage;
|
||||
/**
|
||||
* Merges route records.
|
||||
*
|
||||
* @internal
|
||||
*
|
||||
* @param main - main route record
|
||||
* @param routeRecords - route records to merge
|
||||
* @returns merged route record
|
||||
*/
|
||||
declare function _mergeRouteRecord(main: RouteRecordRaw, ...routeRecords: Partial<RouteRecordRaw>[]): RouteRecordRaw;
|
||||
/**
|
||||
* Type to define a page. Can be augmented to add custom properties.
|
||||
*/
|
||||
interface DefinePage extends Partial<Omit<RouteRecordRaw, 'children' | 'components' | 'component' | 'name'>> {
|
||||
/**
|
||||
* A route name. If not provided, the name will be generated based on the file path.
|
||||
* Can be set to `false` to remove the name from types.
|
||||
*/
|
||||
name?: string | false;
|
||||
/**
|
||||
* Custom parameters for the route. Requires `experimental.paramParsers` enabled.
|
||||
*
|
||||
* @experimental
|
||||
*/
|
||||
params?: {
|
||||
path?: Record<string, ParamParserType>;
|
||||
/**
|
||||
* Parameters extracted from the query.
|
||||
*/
|
||||
query?: Record<string, DefinePageQueryParamOptions | ParamParserType>;
|
||||
};
|
||||
}
|
||||
type ParamParserType_Native = 'int' | 'bool';
|
||||
type ParamParserType = (TypesConfig extends Record<'ParamParsers', infer ParamParsers> ? ParamParsers : never) | ParamParserType_Native;
|
||||
/**
|
||||
* Configures how to extract a route param from a specific query parameter.
|
||||
*/
|
||||
interface DefinePageQueryParamOptions<T = unknown> {
|
||||
/**
|
||||
* The type of the query parameter. Allowed values are native param parsers
|
||||
* and any parser in the {@link https://uvr.esm.is/TODO | params folder }. If
|
||||
* not provided, the value will kept as is.
|
||||
*/
|
||||
parser?: ParamParserType;
|
||||
/**
|
||||
* Default value if the query parameter is missing or if the match fails
|
||||
* (e.g. a invalid number is passed to the int param parser). If not provided
|
||||
* and the param is not required, the route will match with undefined.
|
||||
*/
|
||||
default?: (() => T) | T;
|
||||
/**
|
||||
* How to format the query parameter value.
|
||||
*
|
||||
* - 'value' - keep the first value only and pass that to parser
|
||||
* - 'array' - keep all values (even one or none) as an array and pass that to parser
|
||||
*
|
||||
* @default 'value'
|
||||
*/
|
||||
format?: 'value' | 'array';
|
||||
/**
|
||||
* Whether this query parameter is required. If true and the parameter is
|
||||
* missing (and no default is provided), the route will not match.
|
||||
*
|
||||
* @default false
|
||||
*/
|
||||
required?: boolean;
|
||||
}
|
||||
//#endregion
|
||||
//#region src/experimental/data-loaders/defineLoader.d.ts
|
||||
/**
|
||||
* Creates a data loader composable that can be exported by pages to attach the data loading to a route. In this version `data` is always defined.
|
||||
*
|
||||
* @param name - name of the route
|
||||
* @param loader - function that returns a promise with the data
|
||||
* @param options - options to configure the data loader
|
||||
*/
|
||||
declare function defineBasicLoader<Name extends keyof RouteMap, Data>(name: Name, loader: DefineLoaderFn<Data, DataLoaderContext, RouteLocationNormalizedLoaded<Name>>, options?: DefineDataLoaderOptions_DefinedData): UseDataLoaderBasic_DefinedData<Data>;
|
||||
/**
|
||||
* Creates a data loader composable that can be exported by pages to attach the data loading to a route. In this version, `data` can be `undefined`.
|
||||
*
|
||||
* @param name - name of the route
|
||||
* @param loader - function that returns a promise with the data
|
||||
* @param options - options to configure the data loader
|
||||
*/
|
||||
declare function defineBasicLoader<Name extends keyof RouteMap, Data>(name: Name, loader: DefineLoaderFn<Data, DataLoaderContext, RouteLocationNormalizedLoaded<Name>>, options: DefineDataLoaderOptions_LaxData): UseDataLoaderBasic_LaxData<Data>;
|
||||
/**
|
||||
* Creates a data loader composable that can be exported by pages to attach the data loading to a route. In this version `data` is always defined.
|
||||
*
|
||||
* @param loader - function that returns a promise with the data
|
||||
* @param options - options to configure the data loader
|
||||
*/
|
||||
declare function defineBasicLoader<Data>(loader: DefineLoaderFn<Data, DataLoaderContext, RouteLocationNormalizedLoaded>, options?: DefineDataLoaderOptions_DefinedData): UseDataLoaderBasic_DefinedData<Data>;
|
||||
/**
|
||||
* Creates a data loader composable that can be exported by pages to attach the data loading to a route. In this version, `data` can be `undefined`.
|
||||
*
|
||||
* @param loader - function that returns a promise with the data
|
||||
* @param options - options to configure the data loader
|
||||
*/
|
||||
declare function defineBasicLoader<Data>(loader: DefineLoaderFn<Data, DataLoaderContext, RouteLocationNormalizedLoaded>, options: DefineDataLoaderOptions_LaxData): UseDataLoaderBasic_LaxData<Data>;
|
||||
interface DefineDataLoaderOptions_LaxData extends DefineDataLoaderOptionsBase_LaxData {
|
||||
/**
|
||||
* Key to use for SSR state. This will be used to read the initial data from `initialData`'s object.
|
||||
*/
|
||||
key?: string;
|
||||
}
|
||||
interface DefineDataLoaderOptions_DefinedData extends DefineDataLoaderOptionsBase_DefinedData {
|
||||
key?: string;
|
||||
}
|
||||
/**
|
||||
* @deprecated use {@link DefineDataLoaderOptions_LaxData} instead
|
||||
*/
|
||||
type DefineDataLoaderOptions = DefineDataLoaderOptions_LaxData;
|
||||
interface DataLoaderContext extends DataLoaderContextBase {}
|
||||
/**
|
||||
* Symbol used to store the data in the router so it can be retrieved after the initial navigation.
|
||||
* @internal
|
||||
*/
|
||||
declare const SERVER_INITIAL_DATA_KEY: unique symbol;
|
||||
/**
|
||||
* Initial data generated on server and consumed on client.
|
||||
* @internal
|
||||
*/
|
||||
declare const INITIAL_DATA_KEY: unique symbol;
|
||||
declare module '../../router' {
|
||||
interface Router {
|
||||
/**
|
||||
* Gives access to the initial state during rendering. Should be set to `false` once it's consumed.
|
||||
* @internal
|
||||
*/
|
||||
[SERVER_INITIAL_DATA_KEY]?: Record<string, unknown> | false;
|
||||
[INITIAL_DATA_KEY]?: Record<string, unknown> | false;
|
||||
}
|
||||
}
|
||||
interface UseDataLoaderBasic_LaxData<Data> extends UseDataLoader<Data | undefined, ErrorDefault> {}
|
||||
/**
|
||||
* @deprecated use {@link UseDataLoaderBasic_LaxData} instead
|
||||
*/
|
||||
type UseDataLoaderBasic<Data> = UseDataLoaderBasic_LaxData<Data>;
|
||||
interface UseDataLoaderBasic_DefinedData<Data> extends UseDataLoader<Data, ErrorDefault> {}
|
||||
interface DataLoaderBasicEntry<TData, TError = unknown, TDataInitial extends TData | undefined = TData | undefined> extends DataLoaderEntryBase<TData, TError, TDataInitial> {}
|
||||
//#endregion
|
||||
//#region src/experimental/index.d.ts
|
||||
/**
|
||||
* @deprecated Use {@link reroute} instead.
|
||||
*/
|
||||
declare class NavigationResult extends NavigationResult$1 {
|
||||
constructor(...args: ConstructorParameters<typeof NavigationResult$1>);
|
||||
}
|
||||
//#endregion
|
||||
export { type DataLoaderBasicEntry, type DataLoaderContext, type DataLoaderContextBase, type DataLoaderEntryBase, DataLoaderPlugin, type DataLoaderPluginOptions, type DefineDataLoaderOptions, type DefineDataLoaderOptionsBase_DefinedData, type DefineDataLoaderOptionsBase_LaxData, type DefineDataLoaderOptions_DefinedData, type DefineDataLoaderOptions_LaxData, type DefineLoaderFn, type DefinePage, type DefinePageQueryParamOptions, type EXPERIMENTAL_RouteRecordNormalized, type EXPERIMENTAL_RouteRecordNormalized_Group, type EXPERIMENTAL_RouteRecordNormalized_Matchable, type EXPERIMENTAL_RouteRecordRaw, type EXPERIMENTAL_RouteRecord_Base, type EXPERIMENTAL_RouteRecord_Group, type EXPERIMENTAL_RouteRecord_Matchable, type EXPERIMENTAL_Router, type EXPERIMENTAL_RouterOptions, type EXPERIMENTAL_RouterOptions_Base, type EXPERIMENTAL_Router_Base, type EmptyParams, type ErrorDefault, type MatcherParamsFormatted, type MatcherPattern, type MatcherPatternHash, type MatcherPatternPath, MatcherPatternPathDynamic, type MatcherPatternPathDynamic_ParamOptions, MatcherPatternPathStatic, type MatcherPatternQuery, MatcherPatternQueryParam, type MatcherQueryParams, type MatcherQueryParamsValue, NavigationResult, PARAM_PARSER_BOOL, PARAM_PARSER_INT, type ParamParser, type ParamParserType, type ParamParserType_Native, type SetupLoaderGuardOptions, type UseDataLoader, type UseDataLoaderBasic, type UseDataLoaderBasic_DefinedData, type UseDataLoaderBasic_LaxData, type UseDataLoaderInternals, type UseDataLoaderResult, type ExtractParamParserType as _ExtractParamParserType, MatchMiss as _MatchMiss, NavigationResult$1 as _NavigationResult, _mergeRouteRecord, normalizeParamParser as _normalizeParamParser, createFixedResolver, defineBasicLoader, definePage, defineParamParser, definePathParamParser, defineQueryParamParser, experimental_createRouter, getCurrentContext, miss, normalizeRouteRecord, reroute, setCurrentContext, toLazyValue, trackRoute, useIsDataLoading, withLoaderContext };
|
||||
+986
@@ -0,0 +1,986 @@
|
||||
/*!
|
||||
* vue-router v5.0.6
|
||||
* (c) 2026 Eduardo San Martin Morote
|
||||
* @license MIT
|
||||
*/
|
||||
import { a as routerKey, b as isBrowser, c as ErrorTypes, d as isNavigationFailure, h as isArray$1, i as routeLocationKey, m as identityFn, n as useRouter, o as routerViewLocationKey, p as assign, t as useRoute, u as createRouterError, y as noop } from "../useApi-DtucMc-S.js";
|
||||
import { A as resolveRelativePath, E as isSameRouteLocation, F as encodeParam, I as encodePath, L as warn$2, N as decode, T as START_LOCATION_NORMALIZED, _ as saveScrollPosition, b as NavigationType, c as useCallbacks, d as stringifyQuery, g as getScrollKey, h as getSavedScrollPosition, i as guardToPromiseFn, k as parseURL, l as normalizeQuery, m as computeScrollPosition, n as extractChangingRecords, r as extractComponentsGuards, t as addDevtools, u as parseQuery, v as scrollToPosition, w as NEW_stringifyURL } from "../devtools-BPov6AZY.js";
|
||||
import { _ as LOADER_SET_KEY, b as toLazyValue, c as setCurrentContext, d as ABORT_CONTROLLER_KEY, f as APP_KEY, g as LOADER_ENTRIES_KEY, h as IS_USE_DATA_LOADER_KEY, i as useIsDataLoading, l as trackRoute, m as IS_SSR_KEY, n as NavigationResult$1, o as getCurrentContext, r as reroute, t as DataLoaderPlugin, u as withLoaderContext, v as PENDING_LOCATION_KEY, y as STAGED_NO_VALUE } from "../navigation-guard-DK6v16ki.js";
|
||||
import { nextTick, shallowReactive, shallowRef, toValue, unref, warn } from "vue";
|
||||
import "scule";
|
||||
|
||||
//#region src/experimental/router.ts
|
||||
function normalizeRouteRecord(record) {
|
||||
const normalizedRecord = {
|
||||
meta: {},
|
||||
props: {},
|
||||
...record,
|
||||
instances: {},
|
||||
leaveGuards: /* @__PURE__ */ new Set(),
|
||||
updateGuards: /* @__PURE__ */ new Set()
|
||||
};
|
||||
Object.defineProperty(normalizedRecord, "mods", { value: {} });
|
||||
return normalizedRecord;
|
||||
}
|
||||
/**
|
||||
* Creates an experimental Router that allows passing a resolver instead of a
|
||||
* routes array. This router does not have `addRoute()` and `removeRoute()`
|
||||
* methods and is meant to be used with file-based routing thanks to
|
||||
* vue-router/vite or vue-router/unplugin resolver generation in
|
||||
* `'vue-router/auto-resolver'`.
|
||||
*
|
||||
* @param options - Options to initialize the router
|
||||
*/
|
||||
function experimental_createRouter(options) {
|
||||
let { resolver, stringifyQuery: stringifyQuery$1 = stringifyQuery, history: routerHistory } = options;
|
||||
const beforeGuards = useCallbacks();
|
||||
const beforeResolveGuards = useCallbacks();
|
||||
const afterGuards = useCallbacks();
|
||||
const currentRoute = shallowRef(START_LOCATION_NORMALIZED);
|
||||
let pendingLocation = START_LOCATION_NORMALIZED;
|
||||
if (isBrowser && options.scrollBehavior) history.scrollRestoration = "manual";
|
||||
function resolve(...[to, currentLocation]) {
|
||||
const matchedRoute = resolver.resolve(to, currentLocation ?? (typeof to === "string" ? currentRoute.value : void 0));
|
||||
const href = routerHistory.createHref(matchedRoute.fullPath);
|
||||
if (process.env.NODE_ENV !== "production") {
|
||||
if (href.startsWith("//")) warn(`Location ${JSON.stringify(to)} resolved to "${href}". A resolved location cannot start with multiple slashes.`);
|
||||
if (!matchedRoute.matched.length) warn(`No match found for location with path "${to}"`);
|
||||
}
|
||||
return assign(matchedRoute, {
|
||||
redirectedFrom: void 0,
|
||||
href,
|
||||
meta: mergeMetaFields(matchedRoute.matched)
|
||||
});
|
||||
}
|
||||
function checkCanceledNavigation(to, from) {
|
||||
if (pendingLocation !== to) return createRouterError(ErrorTypes.NAVIGATION_CANCELLED, {
|
||||
from,
|
||||
to
|
||||
});
|
||||
}
|
||||
const push = (...args) => pushWithRedirect(resolve(...args));
|
||||
const replace = (...args) => pushWithRedirect(resolve(...args), true);
|
||||
function handleRedirectRecord(to, from) {
|
||||
const redirect = to.matched.at(-1)?.redirect;
|
||||
if (redirect) return resolver.resolve(typeof redirect === "function" ? redirect(to, from) : redirect, from);
|
||||
}
|
||||
function pushWithRedirect(to, replace, redirectedFrom) {
|
||||
replace = to.replace ?? replace;
|
||||
pendingLocation = to;
|
||||
const from = currentRoute.value;
|
||||
const data = to.state;
|
||||
const force = to.force;
|
||||
const shouldRedirect = handleRedirectRecord(to, from);
|
||||
if (shouldRedirect) return pushWithRedirect({
|
||||
...resolve(shouldRedirect, currentRoute.value),
|
||||
state: typeof shouldRedirect === "object" ? assign({}, data, shouldRedirect.state) : data,
|
||||
force
|
||||
}, replace, redirectedFrom || to);
|
||||
const toLocation = to;
|
||||
toLocation.redirectedFrom = redirectedFrom;
|
||||
let failure;
|
||||
if (!force && isSameRouteLocation(stringifyQuery$1, from, to)) {
|
||||
failure = createRouterError(ErrorTypes.NAVIGATION_DUPLICATED, {
|
||||
to: toLocation,
|
||||
from
|
||||
});
|
||||
handleScroll(from, from, true, false);
|
||||
}
|
||||
return (failure ? Promise.resolve(failure) : navigate(toLocation, from)).catch((error) => isNavigationFailure(error) ? isNavigationFailure(error, ErrorTypes.NAVIGATION_GUARD_REDIRECT) ? error : markAsReady(error) : triggerError(error, toLocation, from)).then((failure) => {
|
||||
if (failure) {
|
||||
if (isNavigationFailure(failure, ErrorTypes.NAVIGATION_GUARD_REDIRECT)) {
|
||||
if (process.env.NODE_ENV !== "production" && isSameRouteLocation(stringifyQuery$1, resolve(failure.to), toLocation) && redirectedFrom && (redirectedFrom._count = redirectedFrom._count ? redirectedFrom._count + 1 : 1) > 30) {
|
||||
warn(`Detected a possibly infinite redirection in a navigation guard when going from "${from.fullPath}" to "${toLocation.fullPath}". Aborting to avoid a Stack Overflow.\n Are you always returning a new location within a navigation guard? That would lead to this error. Only return when redirecting or aborting, that should fix this. This might break in production if not fixed.`);
|
||||
return Promise.reject(/* @__PURE__ */ new Error("Infinite redirect in navigation guard"));
|
||||
}
|
||||
return pushWithRedirect({
|
||||
...resolve(failure.to, currentRoute.value),
|
||||
state: typeof failure.to === "object" ? assign({}, data, failure.to.state) : data,
|
||||
force
|
||||
}, replace, redirectedFrom || toLocation);
|
||||
}
|
||||
} else failure = finalizeNavigation(toLocation, from, true, replace, data);
|
||||
triggerAfterEach(toLocation, from, failure);
|
||||
return failure;
|
||||
});
|
||||
}
|
||||
/**
|
||||
* Helper to reject and skip all navigation guards if a new navigation happened
|
||||
* @param to
|
||||
* @param from
|
||||
*/
|
||||
function checkCanceledNavigationAndReject(to, from) {
|
||||
const error = checkCanceledNavigation(to, from);
|
||||
return error ? Promise.reject(error) : Promise.resolve();
|
||||
}
|
||||
function runWithContext(fn) {
|
||||
const app = installedApps.values().next().value;
|
||||
return app?.runWithContext ? app.runWithContext(fn) : fn();
|
||||
}
|
||||
function navigate(to, from) {
|
||||
let guards;
|
||||
const [leavingRecords, updatingRecords, enteringRecords] = extractChangingRecords(to, from);
|
||||
guards = extractComponentsGuards(leavingRecords.reverse(), "beforeRouteLeave", to, from);
|
||||
for (const record of leavingRecords) record.leaveGuards.forEach((guard) => {
|
||||
guards.push(guardToPromiseFn(guard, to, from));
|
||||
});
|
||||
const canceledNavigationCheck = checkCanceledNavigationAndReject.bind(null, to, from);
|
||||
guards.push(canceledNavigationCheck);
|
||||
return runGuardQueue(guards).then(() => {
|
||||
guards = [];
|
||||
for (const guard of beforeGuards.list()) guards.push(guardToPromiseFn(guard, to, from));
|
||||
guards.push(canceledNavigationCheck);
|
||||
return runGuardQueue(guards);
|
||||
}).then(() => {
|
||||
guards = extractComponentsGuards(updatingRecords, "beforeRouteUpdate", to, from);
|
||||
for (const record of updatingRecords) record.updateGuards.forEach((guard) => {
|
||||
guards.push(guardToPromiseFn(guard, to, from));
|
||||
});
|
||||
guards.push(canceledNavigationCheck);
|
||||
return runGuardQueue(guards);
|
||||
}).then(() => {
|
||||
guards = [];
|
||||
for (const record of enteringRecords) if (record.beforeEnter) if (isArray$1(record.beforeEnter)) for (const beforeEnter of record.beforeEnter) guards.push(guardToPromiseFn(beforeEnter, to, from));
|
||||
else guards.push(guardToPromiseFn(record.beforeEnter, to, from));
|
||||
guards.push(canceledNavigationCheck);
|
||||
return runGuardQueue(guards);
|
||||
}).then(() => {
|
||||
to.matched.forEach((record) => record.enterCallbacks = {});
|
||||
guards = extractComponentsGuards(enteringRecords, "beforeRouteEnter", to, from, runWithContext);
|
||||
guards.push(canceledNavigationCheck);
|
||||
return runGuardQueue(guards);
|
||||
}).then(() => {
|
||||
guards = [];
|
||||
for (const guard of beforeResolveGuards.list()) guards.push(guardToPromiseFn(guard, to, from));
|
||||
guards.push(canceledNavigationCheck);
|
||||
return runGuardQueue(guards);
|
||||
}).catch((err) => isNavigationFailure(err, ErrorTypes.NAVIGATION_CANCELLED) ? err : Promise.reject(err));
|
||||
}
|
||||
function triggerAfterEach(to, from, failure) {
|
||||
afterGuards.list().forEach((guard) => runWithContext(() => guard(to, from, failure)));
|
||||
}
|
||||
/**
|
||||
* - Cleans up any navigation guards
|
||||
* - Changes the url if necessary
|
||||
* - Calls the scrollBehavior
|
||||
*/
|
||||
function finalizeNavigation(toLocation, from, isPush, replace, data) {
|
||||
const error = checkCanceledNavigation(toLocation, from);
|
||||
if (error) return error;
|
||||
const isFirstNavigation = from === START_LOCATION_NORMALIZED;
|
||||
const state = !isBrowser ? {} : history.state;
|
||||
if (isPush) if (replace || isFirstNavigation) routerHistory.replace(toLocation.fullPath, assign({ scroll: isFirstNavigation && state && state.scroll }, data));
|
||||
else routerHistory.push(toLocation.fullPath, data);
|
||||
currentRoute.value = toLocation;
|
||||
handleScroll(toLocation, from, isPush, isFirstNavigation);
|
||||
markAsReady();
|
||||
}
|
||||
let removeHistoryListener;
|
||||
function setupListeners() {
|
||||
if (removeHistoryListener) return;
|
||||
removeHistoryListener = routerHistory.listen((to, _from, info) => {
|
||||
if (!router.listening) return;
|
||||
const toLocation = resolve(to);
|
||||
const shouldRedirect = handleRedirectRecord(toLocation, router.currentRoute.value);
|
||||
if (shouldRedirect) {
|
||||
pushWithRedirect(assign(resolve(shouldRedirect), { force: true }), true, toLocation).catch(noop);
|
||||
return;
|
||||
}
|
||||
pendingLocation = toLocation;
|
||||
const from = currentRoute.value;
|
||||
if (isBrowser) saveScrollPosition(getScrollKey(from.fullPath, info.delta), computeScrollPosition());
|
||||
navigate(toLocation, from).catch((error) => {
|
||||
if (isNavigationFailure(error, ErrorTypes.NAVIGATION_ABORTED | ErrorTypes.NAVIGATION_CANCELLED)) return error;
|
||||
if (isNavigationFailure(error, ErrorTypes.NAVIGATION_GUARD_REDIRECT)) {
|
||||
pushWithRedirect(assign(resolve(error.to), { force: true }), void 0, toLocation).then((failure) => {
|
||||
if (isNavigationFailure(failure, ErrorTypes.NAVIGATION_ABORTED | ErrorTypes.NAVIGATION_DUPLICATED) && !info.delta && info.type === NavigationType.pop) routerHistory.go(-1, false);
|
||||
}).catch(noop);
|
||||
return Promise.reject();
|
||||
}
|
||||
if (info.delta) routerHistory.go(-info.delta, false);
|
||||
return triggerError(error, toLocation, from);
|
||||
}).then((failure) => {
|
||||
failure = failure || finalizeNavigation(toLocation, from, false);
|
||||
if (failure) {
|
||||
if (info.delta && !isNavigationFailure(failure, ErrorTypes.NAVIGATION_CANCELLED)) routerHistory.go(-info.delta, false);
|
||||
else if (info.type === NavigationType.pop && isNavigationFailure(failure, ErrorTypes.NAVIGATION_ABORTED | ErrorTypes.NAVIGATION_DUPLICATED)) routerHistory.go(-1, false);
|
||||
}
|
||||
triggerAfterEach(toLocation, from, failure);
|
||||
}).catch(noop);
|
||||
});
|
||||
}
|
||||
let readyHandlers = useCallbacks();
|
||||
let errorListeners = useCallbacks();
|
||||
let ready;
|
||||
/**
|
||||
* Trigger errorListeners added via onError and throws the error as well
|
||||
*
|
||||
* @param error - error to throw
|
||||
* @param to - location we were navigating to when the error happened
|
||||
* @param from - location we were navigating from when the error happened
|
||||
* @returns the error as a rejected promise
|
||||
*/
|
||||
function triggerError(error, to, from) {
|
||||
markAsReady(error);
|
||||
const list = errorListeners.list();
|
||||
if (list.length) list.forEach((handler) => handler(error, to, from));
|
||||
else {
|
||||
if (process.env.NODE_ENV !== "production") warn("uncaught error during route navigation:");
|
||||
console.error(error);
|
||||
}
|
||||
return Promise.reject(error);
|
||||
}
|
||||
function isReady() {
|
||||
if (ready && currentRoute.value !== START_LOCATION_NORMALIZED) return Promise.resolve();
|
||||
return new Promise((resolve, reject) => {
|
||||
readyHandlers.add([resolve, reject]);
|
||||
});
|
||||
}
|
||||
function markAsReady(err) {
|
||||
if (!ready) {
|
||||
ready = !err;
|
||||
setupListeners();
|
||||
readyHandlers.list().forEach(([resolve, reject]) => err ? reject(err) : resolve());
|
||||
readyHandlers.reset();
|
||||
}
|
||||
return err;
|
||||
}
|
||||
function handleScroll(to, from, isPush, isFirstNavigation) {
|
||||
const { scrollBehavior } = options;
|
||||
if (!isBrowser || !scrollBehavior) return Promise.resolve();
|
||||
const scrollPosition = !isPush && getSavedScrollPosition(getScrollKey(to.fullPath, 0)) || (isFirstNavigation || !isPush) && history.state && history.state.scroll || null;
|
||||
return nextTick().then(() => scrollBehavior(to, from, scrollPosition)).then((position) => position && scrollToPosition(position)).catch((err) => triggerError(err, to, from));
|
||||
}
|
||||
const go = (delta) => routerHistory.go(delta);
|
||||
let started;
|
||||
const installedApps = /* @__PURE__ */ new Set();
|
||||
const router = {
|
||||
currentRoute,
|
||||
listening: true,
|
||||
hasRoute: (name) => !!resolver.getRoute(name),
|
||||
getRoutes: () => resolver.getRoutes(),
|
||||
resolve,
|
||||
options,
|
||||
push,
|
||||
replace,
|
||||
go,
|
||||
back: () => go(-1),
|
||||
forward: () => go(1),
|
||||
beforeEach: beforeGuards.add,
|
||||
beforeResolve: beforeResolveGuards.add,
|
||||
afterEach: afterGuards.add,
|
||||
onError: errorListeners.add,
|
||||
isReady,
|
||||
install(app) {
|
||||
app.config.globalProperties.$router = router;
|
||||
Object.defineProperty(app.config.globalProperties, "$route", {
|
||||
enumerable: true,
|
||||
get: () => unref(currentRoute)
|
||||
});
|
||||
if (isBrowser && !started && currentRoute.value === START_LOCATION_NORMALIZED) {
|
||||
started = true;
|
||||
push(routerHistory.location).catch((err) => {
|
||||
if (process.env.NODE_ENV !== "production") warn("Unexpected error on initial navigation:", err);
|
||||
});
|
||||
}
|
||||
const reactiveRoute = {};
|
||||
for (const key in START_LOCATION_NORMALIZED) Object.defineProperty(reactiveRoute, key, {
|
||||
get: () => currentRoute.value[key],
|
||||
enumerable: true
|
||||
});
|
||||
app.provide(routerKey, router);
|
||||
app.provide(routeLocationKey, shallowReactive(reactiveRoute));
|
||||
app.provide(routerViewLocationKey, currentRoute);
|
||||
installedApps.add(app);
|
||||
app.onUnmount(() => {
|
||||
installedApps.delete(app);
|
||||
if (installedApps.size < 1) {
|
||||
pendingLocation = START_LOCATION_NORMALIZED;
|
||||
removeHistoryListener && removeHistoryListener();
|
||||
removeHistoryListener = null;
|
||||
currentRoute.value = START_LOCATION_NORMALIZED;
|
||||
started = false;
|
||||
ready = false;
|
||||
}
|
||||
});
|
||||
if ((process.env.NODE_ENV !== "production" || __VUE_PROD_DEVTOOLS__) && isBrowser) addDevtools(app, router, resolver);
|
||||
}
|
||||
};
|
||||
function runGuardQueue(guards) {
|
||||
return guards.reduce((promise, guard) => promise.then(() => runWithContext(guard)), Promise.resolve());
|
||||
}
|
||||
if (process.env.NODE_ENV !== "production") router._hmrReplaceResolver = (newResolver) => {
|
||||
resolver = newResolver;
|
||||
};
|
||||
return router;
|
||||
}
|
||||
/**
|
||||
* Merge meta fields of an array of records
|
||||
*
|
||||
* @param matched - array of matched records
|
||||
*/
|
||||
function mergeMetaFields(matched) {
|
||||
return assign({}, ...matched.map((r) => r.meta));
|
||||
}
|
||||
|
||||
//#endregion
|
||||
//#region src/experimental/route-resolver/resolver-abstract.ts
|
||||
/**
|
||||
* Common properties for a location that couldn't be matched. This ensures
|
||||
* having the same name while having a `path`, `query` and `hash` that change.
|
||||
*/
|
||||
const NO_MATCH_LOCATION = {
|
||||
name: process.env.NODE_ENV !== "production" ? Symbol("no-match") : Symbol(),
|
||||
params: {},
|
||||
matched: []
|
||||
};
|
||||
|
||||
//#endregion
|
||||
//#region src/experimental/route-resolver/resolver-fixed.ts
|
||||
/**
|
||||
* Build the `matched` array of a record that includes all parent records from the root to the current one.
|
||||
*/
|
||||
function buildMatched(record) {
|
||||
const matched = [];
|
||||
let node = record;
|
||||
while (node) {
|
||||
matched.unshift(node);
|
||||
node = node.parent;
|
||||
}
|
||||
return matched;
|
||||
}
|
||||
/**
|
||||
* Creates a fixed resolver that must have all records defined at creation
|
||||
* time.
|
||||
*
|
||||
* @template TRecord - extended type of the records
|
||||
* @param {TRecord[]} records - Ordered array of records that will be used to resolve routes
|
||||
* @returns a resolver that can be passed to the router
|
||||
*/
|
||||
function createFixedResolver(records) {
|
||||
const recordMap = /* @__PURE__ */ new Map();
|
||||
for (const record of records) if (!record.aliasOf) recordMap.set(record.name, record);
|
||||
function validateMatch(record, url) {
|
||||
const pathParams = record.path.match(url.path);
|
||||
const hashParams = record.hash?.match(url.hash);
|
||||
const matched = buildMatched(record);
|
||||
const queryParams = Object.assign({}, ...matched.flatMap((record) => record.query?.map((query) => query.match(url.query))));
|
||||
return [matched, {
|
||||
...pathParams,
|
||||
...queryParams,
|
||||
...hashParams
|
||||
}];
|
||||
}
|
||||
function resolve(...[to, currentLocation]) {
|
||||
if (typeof to === "object" && (to.name || to.path == null)) {
|
||||
if (process.env.NODE_ENV !== "production" && to.name == null && currentLocation == null) {
|
||||
warn$2(`Cannot resolve relative location "${JSON.stringify(to)}"without a "name" or a current location. This will crash in production.`, to);
|
||||
const query = normalizeQuery(to.query);
|
||||
const hash = to.hash ?? "";
|
||||
const path = to.path ?? "/";
|
||||
return {
|
||||
...to,
|
||||
...NO_MATCH_LOCATION,
|
||||
fullPath: NEW_stringifyURL(stringifyQuery, path, query, hash),
|
||||
path,
|
||||
query,
|
||||
hash
|
||||
};
|
||||
}
|
||||
const name = to.name ?? currentLocation.name;
|
||||
const record = recordMap.get(name);
|
||||
if (process.env.NODE_ENV !== "production") {
|
||||
if (!record || !name) throw new Error(`Record "${String(name)}" not found`);
|
||||
if (typeof to === "object" && to.hash && !to.hash.startsWith("#")) warn$2(`A "hash" should always start with the character "#". Replace "${to.hash}" with "#${to.hash}".`);
|
||||
}
|
||||
let params = {
|
||||
...currentLocation?.params,
|
||||
...to.params
|
||||
};
|
||||
const path = record.path.build(params);
|
||||
const hash = record.hash?.build(params) ?? to.hash ?? currentLocation?.hash ?? "";
|
||||
let matched = buildMatched(record);
|
||||
const query = Object.assign({
|
||||
...currentLocation?.query,
|
||||
...normalizeQuery(to.query)
|
||||
}, ...matched.flatMap((record) => record.query?.map((query) => query.build(params))));
|
||||
const url = {
|
||||
...to,
|
||||
fullPath: NEW_stringifyURL(stringifyQuery, path, query, hash),
|
||||
path,
|
||||
hash,
|
||||
query
|
||||
};
|
||||
[matched, params] = validateMatch(record, url);
|
||||
return {
|
||||
...url,
|
||||
name,
|
||||
matched,
|
||||
params
|
||||
};
|
||||
} else {
|
||||
let url;
|
||||
if (typeof to === "string") url = parseURL(parseQuery, to, currentLocation?.path);
|
||||
else {
|
||||
const query = normalizeQuery(to.query);
|
||||
const path = resolveRelativePath(to.path, currentLocation?.path || "/");
|
||||
url = {
|
||||
...to,
|
||||
fullPath: NEW_stringifyURL(stringifyQuery, path, query, to.hash),
|
||||
path,
|
||||
query,
|
||||
hash: to.hash || ""
|
||||
};
|
||||
}
|
||||
let record;
|
||||
let matched;
|
||||
let parsedParams;
|
||||
for (record of records) try {
|
||||
[matched, parsedParams] = validateMatch(record, url);
|
||||
break;
|
||||
} catch {}
|
||||
if (!parsedParams || !matched) return {
|
||||
...url,
|
||||
...NO_MATCH_LOCATION
|
||||
};
|
||||
return {
|
||||
...url,
|
||||
name: record.name,
|
||||
params: parsedParams,
|
||||
matched
|
||||
};
|
||||
}
|
||||
}
|
||||
return {
|
||||
resolve,
|
||||
getRoutes: () => records,
|
||||
getRoute: (name) => recordMap.get(name)
|
||||
};
|
||||
}
|
||||
|
||||
//#endregion
|
||||
//#region src/experimental/route-resolver/matchers/errors.ts
|
||||
/**
|
||||
* Error throw when a matcher matches by regex but validation fails.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
var MatchMiss = class extends Error {
|
||||
name = "MatchMiss";
|
||||
};
|
||||
/**
|
||||
* Helper to throw a {@link MatchMiss} error.
|
||||
* @param args - Arguments to pass to the `MatchMiss` constructor.
|
||||
*
|
||||
* @example
|
||||
* ```ts
|
||||
* miss()
|
||||
* // in a number param matcher
|
||||
* miss('Number must be finite')
|
||||
* ```
|
||||
*/
|
||||
const miss = (...args) => {
|
||||
throw new MatchMiss(...args);
|
||||
};
|
||||
|
||||
//#endregion
|
||||
//#region src/experimental/route-resolver/matchers/matcher-pattern.ts
|
||||
/**
|
||||
* Allows matching a static path.
|
||||
*
|
||||
* @example
|
||||
* ```ts
|
||||
* const matcher = new MatcherPatternPathStatic('/team')
|
||||
* matcher.match('/team') // {}
|
||||
* matcher.match('/team/123') // throws MatchMiss
|
||||
* matcher.build() // '/team'
|
||||
* ```
|
||||
*/
|
||||
var MatcherPatternPathStatic = class {
|
||||
/**
|
||||
* lowercase version of the path to match against.
|
||||
* This is used to make the matching case insensitive.
|
||||
*/
|
||||
pathi;
|
||||
constructor(path) {
|
||||
this.path = path;
|
||||
this.pathi = path.toLowerCase();
|
||||
}
|
||||
match(path) {
|
||||
if (path.toLowerCase() !== this.pathi) miss();
|
||||
return {};
|
||||
}
|
||||
build() {
|
||||
return this.path;
|
||||
}
|
||||
};
|
||||
/**
|
||||
* Regex to remove trailing slashes from a path.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
const TRAILING_SLASHES_RE = /\/*$/;
|
||||
/**
|
||||
* Handles the `path` part of a URL with dynamic parameters.
|
||||
*/
|
||||
var MatcherPatternPathDynamic = class {
|
||||
/**
|
||||
* Cached keys of the {@link params} object.
|
||||
*/
|
||||
paramsKeys;
|
||||
/**
|
||||
* Creates a new dynamic path matcher.
|
||||
*
|
||||
* @param re - regex to match the path against
|
||||
* @param params - object of param parsers as {@link MatcherPatternPathDynamic_ParamOptions}
|
||||
* @param pathParts - array of path parts, where strings are static parts, 1 are regular params, and 0 are splat params (not encode slash)
|
||||
* @param trailingSlash - whether the path should end with a trailing slash, null means "do not care" (for trailing splat params)
|
||||
*/
|
||||
constructor(re, params, pathParts, trailingSlash = false) {
|
||||
this.re = re;
|
||||
this.params = params;
|
||||
this.pathParts = pathParts;
|
||||
this.trailingSlash = trailingSlash;
|
||||
this.paramsKeys = Object.keys(this.params);
|
||||
}
|
||||
match(path) {
|
||||
if (this.trailingSlash != null && this.trailingSlash === !path.endsWith("/")) miss();
|
||||
const match = path.match(this.re);
|
||||
if (!match) miss();
|
||||
const params = {};
|
||||
for (var i = 0; i < this.paramsKeys.length; i++) {
|
||||
var paramName = this.paramsKeys[i];
|
||||
var [parser, repeatable] = this.params[paramName];
|
||||
var currentMatch = match[i + 1] ?? null;
|
||||
var value = repeatable ? (currentMatch?.split("/") || []).map(decode) : decode(currentMatch);
|
||||
params[paramName] = (parser?.get || identityFn)(value);
|
||||
}
|
||||
if (process.env.NODE_ENV !== "production" && Object.keys(params).length !== Object.keys(this.params).length) warn$2(`Regexp matched ${match.length} params, but ${i} params are defined. Found when matching "${path}" against ${String(this.re)}`);
|
||||
return params;
|
||||
}
|
||||
build(params) {
|
||||
let paramIndex = 0;
|
||||
let paramName;
|
||||
let parser;
|
||||
let repeatable;
|
||||
let optional;
|
||||
let value;
|
||||
const path = "/" + this.pathParts.map((part) => {
|
||||
if (typeof part === "string") return part;
|
||||
else if (typeof part === "number") {
|
||||
paramName = this.paramsKeys[paramIndex++];
|
||||
[parser, repeatable, optional] = this.params[paramName];
|
||||
value = (parser?.set || identityFn)(params[paramName]);
|
||||
if (Array.isArray(value) && !value.length && !optional) miss();
|
||||
return Array.isArray(value) ? value.map(encodeParam).join("/") : (part ? encodeParam : encodePath)(value);
|
||||
} else return part.map((subPart) => {
|
||||
if (typeof subPart === "string") return subPart;
|
||||
paramName = this.paramsKeys[paramIndex++];
|
||||
[parser, repeatable, optional] = this.params[paramName];
|
||||
value = (parser?.set || identityFn)(params[paramName]);
|
||||
if (process.env.NODE_ENV !== "production" && repeatable) {
|
||||
warn$2(`Param "${String(paramName)}" is repeatable, but used in a sub segment of the path: "${this.pathParts.join("")}". Repeated params can only be used as a full path segment: "/file/[ids]+/something-else". This will break in production.`);
|
||||
return Array.isArray(value) ? value.map(encodeParam).join("/") : encodeParam(value);
|
||||
}
|
||||
return encodeParam(value);
|
||||
}).join("");
|
||||
}).filter(identityFn).join("/");
|
||||
/**
|
||||
* If the last part of the path is a splat param and its value is empty, it gets
|
||||
* filteretd out, resulting in a path that doesn't end with a `/` and doesn't even match
|
||||
* with the original splat path: e.g. /teams/[...pathMatch] does not match /teams, so it makes
|
||||
* no sense to build a path it cannot match.
|
||||
*/
|
||||
return this.trailingSlash == null ? path + (!value && path.at(-1) !== "/" ? "/" : "") : path.replace(TRAILING_SLASHES_RE, this.trailingSlash ? "/" : "");
|
||||
}
|
||||
};
|
||||
|
||||
//#endregion
|
||||
//#region src/experimental/route-resolver/matchers/param-parsers/booleans.ts
|
||||
const PARAM_BOOLEAN_SINGLE = {
|
||||
get: (value) => {
|
||||
if (value === void 0) return void 0;
|
||||
if (value == null) return true;
|
||||
const lowercaseValue = value.toLowerCase();
|
||||
if (lowercaseValue === "true") return true;
|
||||
if (lowercaseValue === "false") return false;
|
||||
miss();
|
||||
},
|
||||
set: (value) => value == null ? value : String(value)
|
||||
};
|
||||
const PARAM_BOOLEAN_REPEATABLE = {
|
||||
get: (value) => value.map((v) => {
|
||||
const result = PARAM_BOOLEAN_SINGLE.get(v);
|
||||
return result === void 0 ? false : result;
|
||||
}),
|
||||
set: (value) => value.map((v) => PARAM_BOOLEAN_SINGLE.set(v))
|
||||
};
|
||||
/**
|
||||
* Native Param parser for booleans.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
const PARAM_PARSER_BOOL = {
|
||||
get: (value) => Array.isArray(value) ? PARAM_BOOLEAN_REPEATABLE.get(value) : PARAM_BOOLEAN_SINGLE.get(value),
|
||||
set: (value) => Array.isArray(value) ? PARAM_BOOLEAN_REPEATABLE.set(value) : PARAM_BOOLEAN_SINGLE.set(value)
|
||||
};
|
||||
|
||||
//#endregion
|
||||
//#region src/experimental/route-resolver/matchers/param-parsers/integers.ts
|
||||
const PARAM_INTEGER_SINGLE = {
|
||||
get: (value) => {
|
||||
const num = Number(value);
|
||||
if (value && Number.isSafeInteger(num)) return num;
|
||||
miss();
|
||||
},
|
||||
set: (value) => String(value)
|
||||
};
|
||||
const PARAM_INTEGER_REPEATABLE = {
|
||||
get: (value) => value.filter((v) => v != null).map(PARAM_INTEGER_SINGLE.get),
|
||||
set: (value) => value.map(PARAM_INTEGER_SINGLE.set)
|
||||
};
|
||||
/**
|
||||
* Native Param parser for integers.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
const PARAM_PARSER_INT = {
|
||||
get: (value) => Array.isArray(value) ? PARAM_INTEGER_REPEATABLE.get(value) : value != null ? PARAM_INTEGER_SINGLE.get(value) : null,
|
||||
set: (value) => Array.isArray(value) ? PARAM_INTEGER_REPEATABLE.set(value) : value != null ? PARAM_INTEGER_SINGLE.set(value) : null
|
||||
};
|
||||
|
||||
//#endregion
|
||||
//#region src/experimental/route-resolver/matchers/param-parsers/standard-schema.ts
|
||||
/**
|
||||
* Normalizes a param parser input, converting a StandardSchema-compliant object
|
||||
* into a {@link ParamParser} if needed.
|
||||
*
|
||||
* @param parser - a param parser or a StandardSchema-compliant validator
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
function normalizeParamParser(parser) {
|
||||
return "~standard" in parser ? { get(value) {
|
||||
const result = parser["~standard"].validate(value);
|
||||
if (process.env.NODE_ENV !== "production" && result instanceof Promise) throw new TypeError("async validation is not supported for param parsers");
|
||||
if (result.issues) miss(result.issues.map((issue) => issue.message).join(", "));
|
||||
return result.value;
|
||||
} } : parser;
|
||||
}
|
||||
|
||||
//#endregion
|
||||
//#region src/experimental/route-resolver/matchers/param-parsers/index.ts
|
||||
/**
|
||||
* Default parser for params that will keep values as is, and will use `String()`
|
||||
*/
|
||||
const PARAM_PARSER_DEFAULTS = {
|
||||
get: (value) => value ?? null,
|
||||
set: (value) => value == null ? null : Array.isArray(value) ? value.map((v) => v == null ? null : String(v)) : String(value)
|
||||
};
|
||||
/**
|
||||
* Defines a path param parser.
|
||||
*
|
||||
* @param parser - the parser to define. Will be returned as is.
|
||||
*
|
||||
* @see {@link defineQueryParamParser}
|
||||
* @see {@link defineParamParser}
|
||||
*/
|
||||
/*! #__NO_SIDE_EFFECTS__ */
|
||||
function definePathParamParser(parser) {
|
||||
return parser;
|
||||
}
|
||||
/**
|
||||
* Defines a query param parser. Note that query params can also be used as
|
||||
* path param parsers.
|
||||
*
|
||||
* @param parser - the parser to define. Will be returned as is.
|
||||
*
|
||||
* @see {@link definePathParamParser}
|
||||
* @see {@link defineParamParser}
|
||||
*/
|
||||
/*! #__NO_SIDE_EFFECTS__ */
|
||||
function defineQueryParamParser(parser) {
|
||||
return parser;
|
||||
}
|
||||
/**
|
||||
* Alias for {@link defineQueryParamParser}. Implementing a param parser like this
|
||||
* works for path, query, and hash params.
|
||||
*
|
||||
* @see {@link defineQueryParamParser}
|
||||
* @see {@link definePathParamParser}
|
||||
*/
|
||||
const defineParamParser = defineQueryParamParser;
|
||||
|
||||
//#endregion
|
||||
//#region src/experimental/route-resolver/matchers/matcher-pattern-query.ts
|
||||
/**
|
||||
* Matcher for a specific query parameter. It will read and write the parameter
|
||||
*/
|
||||
var MatcherPatternQueryParam = class {
|
||||
constructor(paramName, queryKey, format, parser = {}, defaultValue, required) {
|
||||
this.paramName = paramName;
|
||||
this.queryKey = queryKey;
|
||||
this.format = format;
|
||||
this.parser = parser;
|
||||
this.defaultValue = defaultValue;
|
||||
this.required = required;
|
||||
}
|
||||
match(query) {
|
||||
const queryValue = query[this.queryKey];
|
||||
let valueBeforeParse = this.format === "value" ? Array.isArray(queryValue) ? queryValue.at(-1) : queryValue : Array.isArray(queryValue) ? queryValue : queryValue == null ? [] : [queryValue];
|
||||
let value;
|
||||
if (Array.isArray(valueBeforeParse)) if (queryValue === void 0 && this.defaultValue !== void 0) value = toValue(this.defaultValue);
|
||||
else try {
|
||||
value = (this.parser.get ?? PARAM_PARSER_DEFAULTS.get)(valueBeforeParse);
|
||||
} catch (error) {
|
||||
if (this.defaultValue === void 0) throw error;
|
||||
value = void 0;
|
||||
}
|
||||
else try {
|
||||
value = valueBeforeParse === void 0 ? valueBeforeParse : (this.parser.get ?? PARAM_PARSER_DEFAULTS.get)(valueBeforeParse);
|
||||
} catch (error) {
|
||||
if (this.defaultValue === void 0) throw error;
|
||||
}
|
||||
if (value === void 0) {
|
||||
if (this.defaultValue !== void 0) value = toValue(this.defaultValue);
|
||||
else if (this.required) miss();
|
||||
}
|
||||
return { [this.paramName]: value };
|
||||
}
|
||||
build(params) {
|
||||
const paramValue = params[this.paramName];
|
||||
if (paramValue === void 0) return {};
|
||||
return { [this.queryKey]: (this.parser.set ?? PARAM_PARSER_DEFAULTS.set)(paramValue) };
|
||||
}
|
||||
};
|
||||
|
||||
//#endregion
|
||||
//#region src/experimental/runtime.ts
|
||||
/**
|
||||
* Helper to define page properties with file-based routing.
|
||||
* **Doesn't do anything**, used for types only.
|
||||
*
|
||||
* @param route - route information to be added to this page
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
const definePage = (route) => route;
|
||||
/**
|
||||
* Merges route records.
|
||||
*
|
||||
* @internal
|
||||
*
|
||||
* @param main - main route record
|
||||
* @param routeRecords - route records to merge
|
||||
* @returns merged route record
|
||||
*/
|
||||
function _mergeRouteRecord(main, ...routeRecords) {
|
||||
return routeRecords.reduce((acc, routeRecord) => {
|
||||
const meta = Object.assign({}, acc.meta, routeRecord.meta);
|
||||
const alias = [].concat(acc.alias || [], routeRecord.alias || []);
|
||||
Object.assign(acc, routeRecord);
|
||||
acc.meta = meta;
|
||||
acc.alias = alias;
|
||||
return acc;
|
||||
}, main);
|
||||
}
|
||||
/**
|
||||
* TODO: native parsers ideas:
|
||||
* - json -> just JSON.parse(value)
|
||||
* - boolean -> 'true' | 'false' -> boolean
|
||||
* - number -> Number(value) -> NaN if not a number
|
||||
*/
|
||||
|
||||
//#endregion
|
||||
//#region src/unplugin/core/utils.ts
|
||||
function warn$1(msg, type = "warn") {
|
||||
console[type](`⚠️ [vue-router]: ${msg}`);
|
||||
}
|
||||
/**
|
||||
* Type safe alternative to Array.isArray
|
||||
* https://github.com/microsoft/TypeScript/pull/48228
|
||||
*/
|
||||
const isArray = Array.isArray;
|
||||
|
||||
//#endregion
|
||||
//#region src/experimental/data-loaders/defineLoader.ts
|
||||
function defineBasicLoader(nameOrLoader, _loaderOrOptions, opts) {
|
||||
const loader = typeof nameOrLoader === "function" ? nameOrLoader : _loaderOrOptions;
|
||||
opts = typeof _loaderOrOptions === "object" ? _loaderOrOptions : opts;
|
||||
const options = {
|
||||
...DEFAULT_DEFINE_LOADER_OPTIONS,
|
||||
...opts,
|
||||
commit: opts?.commit || DEFAULT_DEFINE_LOADER_OPTIONS.commit
|
||||
};
|
||||
function load(to, router, from, parent) {
|
||||
const entries = router[LOADER_ENTRIES_KEY];
|
||||
const isSSR = router[IS_SSR_KEY];
|
||||
if (!entries.has(loader)) entries.set(loader, {
|
||||
data: shallowRef(),
|
||||
isLoading: shallowRef(false),
|
||||
error: shallowRef(null),
|
||||
to,
|
||||
options,
|
||||
children: /* @__PURE__ */ new Set(),
|
||||
resetPending() {
|
||||
this.pendingLoad = null;
|
||||
this.pendingTo = null;
|
||||
},
|
||||
pendingLoad: null,
|
||||
pendingTo: null,
|
||||
staged: STAGED_NO_VALUE,
|
||||
stagedError: null,
|
||||
commit
|
||||
});
|
||||
const entry = entries.get(loader);
|
||||
if (entry.pendingTo === to && entry.pendingLoad) return entry.pendingLoad;
|
||||
const { error, isLoading, data } = entry;
|
||||
const initialRootData = router[INITIAL_DATA_KEY];
|
||||
const key = options.key || "";
|
||||
let initialData = STAGED_NO_VALUE;
|
||||
if (initialRootData && key in initialRootData) {
|
||||
initialData = initialRootData[key];
|
||||
delete initialRootData[key];
|
||||
}
|
||||
if (initialData !== STAGED_NO_VALUE) {
|
||||
data.value = initialData;
|
||||
return entry.pendingLoad = Promise.resolve();
|
||||
}
|
||||
entry.pendingTo = to;
|
||||
isLoading.value = true;
|
||||
const currentContext = getCurrentContext();
|
||||
if (process.env.NODE_ENV !== "production") {
|
||||
if (parent !== currentContext[0]) console.warn(`❌👶 "${options.key}" has a different parent than the current context. This shouldn't be happening. Please report a bug with a reproduction to https://github.com/vuejs/router/`);
|
||||
}
|
||||
setCurrentContext([
|
||||
entry,
|
||||
router,
|
||||
to
|
||||
]);
|
||||
entry.staged = STAGED_NO_VALUE;
|
||||
entry.stagedError = error.value;
|
||||
const currentLoad = Promise.resolve(loader(to, { signal: to.meta[ABORT_CONTROLLER_KEY]?.signal })).then((d) => {
|
||||
if (entry.pendingLoad === currentLoad) if (d instanceof NavigationResult$1) {
|
||||
if (process.env.NODE_ENV !== "production") {
|
||||
console.warn("[vue-router]: Returning a NavigationResult is deprecated. Use reroute() instead, which throws internally.");
|
||||
warnNonExposedLoader({
|
||||
to,
|
||||
options,
|
||||
useDataLoader
|
||||
});
|
||||
}
|
||||
entry.pendingTo = null;
|
||||
throw d;
|
||||
} else {
|
||||
entry.staged = d;
|
||||
entry.stagedError = null;
|
||||
}
|
||||
}).catch((error) => {
|
||||
if (entry.pendingLoad === currentLoad) {
|
||||
if (error instanceof NavigationResult$1) {
|
||||
if (process.env.NODE_ENV !== "production") warnNonExposedLoader({
|
||||
to,
|
||||
options,
|
||||
useDataLoader
|
||||
});
|
||||
entry.pendingTo = null;
|
||||
throw error;
|
||||
}
|
||||
entry.stagedError = error;
|
||||
if (!toLazyValue(options.lazy, to, from) || isSSR) throw error;
|
||||
}
|
||||
}).finally(() => {
|
||||
setCurrentContext(currentContext);
|
||||
if (entry.pendingLoad === currentLoad) {
|
||||
isLoading.value = false;
|
||||
if (options.commit === "immediate" || !router[PENDING_LOCATION_KEY]) entry.commit(to);
|
||||
}
|
||||
});
|
||||
setCurrentContext(currentContext);
|
||||
entry.pendingLoad = currentLoad;
|
||||
return currentLoad;
|
||||
}
|
||||
function commit(to) {
|
||||
if (this.pendingTo === to) {
|
||||
if (process.env.NODE_ENV !== "production") {
|
||||
if (this.staged === STAGED_NO_VALUE && this.stagedError === null) console.warn(`Loader "${options.key}"'s "commit()" was called but there is no staged data.`);
|
||||
}
|
||||
if (this.staged !== STAGED_NO_VALUE) this.data.value = this.staged;
|
||||
this.error.value = this.stagedError;
|
||||
this.staged = STAGED_NO_VALUE;
|
||||
this.stagedError = this.error.value;
|
||||
this.pendingTo = null;
|
||||
this.to = to;
|
||||
for (const childEntry of this.children) childEntry.commit(to);
|
||||
}
|
||||
}
|
||||
const useDataLoader = () => {
|
||||
const currentContext = getCurrentContext();
|
||||
const [parentEntry, _router, _route] = currentContext;
|
||||
const router = _router || useRouter();
|
||||
const route = _route || useRoute();
|
||||
const entries = router[LOADER_ENTRIES_KEY];
|
||||
let entry = entries.get(loader);
|
||||
if (!entry || parentEntry && entry.pendingTo !== route || !entry.pendingLoad) router[APP_KEY].runWithContext(() => load(route, router, void 0, parentEntry));
|
||||
entry = entries.get(loader);
|
||||
if (parentEntry) {
|
||||
if (parentEntry === entry) console.warn(`👶❌ "${options.key}" has itself as parent. This shouldn't be happening. Please report a bug with a reproduction to https://github.com/vuejs/router/`);
|
||||
parentEntry.children.add(entry);
|
||||
}
|
||||
const { data, error, isLoading } = entry;
|
||||
const useDataLoaderResult = {
|
||||
data,
|
||||
error,
|
||||
isLoading,
|
||||
reload: (to = router.currentRoute.value) => router[APP_KEY].runWithContext(() => load(to, router)).then(() => entry.commit(to))
|
||||
};
|
||||
const promise = entry.pendingLoad.then(() => entry.staged === STAGED_NO_VALUE ? data.value : entry.staged).catch((e) => parentEntry ? Promise.reject(e) : null).finally(() => setCurrentContext(currentContext));
|
||||
setCurrentContext(currentContext);
|
||||
return Object.assign(promise, useDataLoaderResult);
|
||||
};
|
||||
useDataLoader[IS_USE_DATA_LOADER_KEY] = true;
|
||||
useDataLoader._ = {
|
||||
load,
|
||||
options,
|
||||
getEntry(router) {
|
||||
return router[LOADER_ENTRIES_KEY].get(loader);
|
||||
}
|
||||
};
|
||||
return useDataLoader;
|
||||
}
|
||||
/**
|
||||
* Dev only warning for loaders that return/throw NavigationResult but are not exposed
|
||||
*
|
||||
* @param to - target location
|
||||
* @param options - options used to define the loader
|
||||
* @param useDataLoader - the data loader composable
|
||||
*/
|
||||
function warnNonExposedLoader({ to, options, useDataLoader }) {
|
||||
const loaders = to.meta[LOADER_SET_KEY];
|
||||
if (loaders && !loaders.has(useDataLoader)) warn$1("A loader returned a NavigationResult but is not registered on the route. Did you forget to \"export\" it from the page component?" + (options.key ? ` (loader key: "${options.key}")` : ""));
|
||||
}
|
||||
const DEFAULT_DEFINE_LOADER_OPTIONS = {
|
||||
lazy: false,
|
||||
server: true,
|
||||
commit: "after-load"
|
||||
};
|
||||
/**
|
||||
* Initial data generated on server and consumed on client.
|
||||
* @internal
|
||||
*/
|
||||
const INITIAL_DATA_KEY = Symbol();
|
||||
|
||||
//#endregion
|
||||
//#region src/experimental/index.ts
|
||||
/**
|
||||
* @deprecated Use {@link reroute} instead.
|
||||
*/
|
||||
var NavigationResult = class extends NavigationResult$1 {
|
||||
constructor(...args) {
|
||||
super(...args);
|
||||
console.warn(`[vue-router]: new NavigationResult(to) is deprecated. Use reroute(to) instead.`);
|
||||
}
|
||||
};
|
||||
|
||||
//#endregion
|
||||
export { DataLoaderPlugin, MatcherPatternPathDynamic, MatcherPatternPathStatic, MatcherPatternQueryParam, NavigationResult, PARAM_PARSER_BOOL, PARAM_PARSER_INT, MatchMiss as _MatchMiss, NavigationResult$1 as _NavigationResult, _mergeRouteRecord, normalizeParamParser as _normalizeParamParser, createFixedResolver, defineBasicLoader, definePage, defineParamParser, definePathParamParser, defineQueryParamParser, experimental_createRouter, getCurrentContext, miss, normalizeRouteRecord, reroute, setCurrentContext, toLazyValue, trackRoute, useIsDataLoading, withLoaderContext };
|
||||
+153
@@ -0,0 +1,153 @@
|
||||
/*!
|
||||
* vue-router v5.0.6
|
||||
* (c) 2026 Eduardo San Martin Morote
|
||||
* @license MIT
|
||||
*/
|
||||
import { _ as _PromiseMerged, a as DefineLoaderFn, c as UseDataLoaderResult, i as DefineDataLoaderOptionsBase_LaxData, n as DataLoaderEntryBase, o as UseDataLoader, p as NavigationResult, r as DefineDataLoaderOptionsBase_DefinedData, t as DataLoaderContextBase, u as ErrorDefault } from "../index-ZwgQvn2r.js";
|
||||
import { Ln as LocationQuery, Nt as RouteLocationNormalizedLoaded, rn as RouteMap } from "../index-D_VEAp3P.js";
|
||||
import { ShallowRef } from "vue";
|
||||
import { EntryKey, UseQueryOptions, UseQueryReturn } from "@pinia/colada";
|
||||
|
||||
//#region src/experimental/data-loaders/defineColadaLoader.d.ts
|
||||
/**
|
||||
* Creates a Pinia Colada data loader with `data` is always defined.
|
||||
*
|
||||
* @param name - name of the route to have typed routes
|
||||
* @param options - options to configure the data loader
|
||||
*/
|
||||
declare function defineColadaLoader<Name extends keyof RouteMap, Data>(name: Name, options: DefineDataColadaLoaderOptions_DefinedData<Name, Data>): UseDataLoaderColada_DefinedData<Data>;
|
||||
/**
|
||||
* Creates a Pinia Colada data loader with `data` is possibly `undefined`.
|
||||
*
|
||||
* @param name - name of the route to have typed routes
|
||||
* @param options - options to configure the data loader
|
||||
*/
|
||||
declare function defineColadaLoader<Name extends keyof RouteMap, Data>(name: Name, options: DefineDataColadaLoaderOptions_LaxData<Name, Data>): UseDataLoaderColada_LaxData<Data>;
|
||||
/**
|
||||
* Creates a Pinia Colada data loader with `data` is always defined.
|
||||
* @param options - options to configure the data loader
|
||||
*/
|
||||
declare function defineColadaLoader<Data>(options: DefineDataColadaLoaderOptions_DefinedData<keyof RouteMap, Data>): UseDataLoaderColada_DefinedData<Data>;
|
||||
/**
|
||||
* Creates a Pinia Colada data loader with `data` is possibly `undefined`.
|
||||
* @param options - options to configure the data loader
|
||||
*/
|
||||
declare function defineColadaLoader<Data>(options: DefineDataColadaLoaderOptions_LaxData<keyof RouteMap, Data>): UseDataLoaderColada_LaxData<Data>;
|
||||
/**
|
||||
* Base type with docs for the options of `defineColadaLoader`.
|
||||
* @internal
|
||||
*/
|
||||
interface _DefineDataColadaLoaderOptions_Common<Name extends keyof RouteMap, Data> extends Omit<UseQueryOptions<Data, ErrorDefault, Data>, 'query' | 'key'> {
|
||||
/**
|
||||
* Key associated with the data and passed to pinia colada
|
||||
* @param to - Route to load the data
|
||||
*/
|
||||
key: EntryKey | ((to: RouteLocationNormalizedLoaded<Name>) => EntryKey);
|
||||
/**
|
||||
* Function that returns a promise with the data.
|
||||
*/
|
||||
query: DefineLoaderFn<Data, DataColadaLoaderContext, RouteLocationNormalizedLoaded<Name>>;
|
||||
}
|
||||
/**
|
||||
* Options for `defineColadaLoader` when the data is possibly `undefined`.
|
||||
*/
|
||||
interface DefineDataColadaLoaderOptions_LaxData<Name extends keyof RouteMap, Data> extends _DefineDataColadaLoaderOptions_Common<Name, Data>, DefineDataLoaderOptionsBase_LaxData {}
|
||||
/**
|
||||
* Options for `defineColadaLoader` when the data is always defined.
|
||||
*/
|
||||
interface DefineDataColadaLoaderOptions_DefinedData<Name extends keyof RouteMap, Data> extends _DefineDataColadaLoaderOptions_Common<Name, Data>, DefineDataLoaderOptionsBase_DefinedData {}
|
||||
/**
|
||||
* @deprecated Use {@link `DefineDataColadaLoaderOptions_LaxData`} instead.
|
||||
*/
|
||||
type DefineDataColadaLoaderOptions<Name extends keyof RouteMap, Data> = DefineDataColadaLoaderOptions_LaxData<Name, Data>;
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
interface DataColadaLoaderContext extends DataLoaderContextBase {}
|
||||
interface UseDataLoaderColadaResult<TData, TError = ErrorDefault, TDataInitial extends TData | undefined = TData | undefined> extends UseDataLoaderResult<TData | TDataInitial, ErrorDefault>, Pick<UseQueryReturn<TData, TError, TDataInitial>, 'isPending' | 'status' | 'asyncStatus' | 'state'> {
|
||||
/**
|
||||
* Equivalent to `useQuery().refetch()`. Refetches the data no matter if its stale or not.
|
||||
* @see reload - It also calls `refetch()` but returns an empty promise
|
||||
*/
|
||||
refetch: (to?: RouteLocationNormalizedLoaded) => ReturnType<UseQueryReturn<TData, TError, TDataInitial>['refetch']>;
|
||||
/**
|
||||
* Equivalent to `useQuery().refresh()`. Refetches the data **only** if it's stale.
|
||||
*/
|
||||
refresh: (to?: RouteLocationNormalizedLoaded) => ReturnType<UseQueryReturn<TData, TError, TDataInitial>['refetch']>;
|
||||
}
|
||||
/**
|
||||
* Data Loader composable returned by `defineColadaLoader()`.
|
||||
*/
|
||||
interface UseDataLoaderColada_LaxData<Data> extends UseDataLoader<Data | undefined, ErrorDefault> {
|
||||
/**
|
||||
* Data Loader composable returned by `defineColadaLoader()`.
|
||||
*
|
||||
* @example
|
||||
* Returns the Data loader data, isLoading, error etc. Meant to be used in `setup()` or `<script setup>` **without `await`**:
|
||||
* ```vue
|
||||
* <script setup>
|
||||
* const { data, isLoading, error } = useUserData()
|
||||
* </script>
|
||||
* ```
|
||||
*
|
||||
* @example
|
||||
* It also returns a promise of the data when used in nested loaders. Note this `data` is **not a ref**. This is not meant to be used in `setup()` or `<script setup>`.
|
||||
* ```ts
|
||||
* export const useUserConnections = defineLoader(async () => {
|
||||
* const user = await useUserData()
|
||||
* return fetchUserConnections(user.id)
|
||||
* })
|
||||
* ```
|
||||
*/
|
||||
(): _PromiseMerged<Exclude<Data, NavigationResult | undefined>, UseDataLoaderColadaResult<Exclude<Data, NavigationResult> | undefined>>;
|
||||
}
|
||||
/**
|
||||
* Data Loader composable returned by `defineColadaLoader()`.
|
||||
*/
|
||||
interface UseDataLoaderColada_DefinedData<TData> extends UseDataLoader<TData, ErrorDefault> {
|
||||
/**
|
||||
* Data Loader composable returned by `defineColadaLoader()`.
|
||||
*
|
||||
* @example
|
||||
* Returns the Data loader data, isLoading, error etc. Meant to be used in `setup()` or `<script setup>` **without `await`**:
|
||||
* ```vue
|
||||
* <script setup>
|
||||
* const { data, isLoading, error } = useUserData()
|
||||
* </script>
|
||||
* ```
|
||||
*
|
||||
* @example
|
||||
* It also returns a promise of the data when used in nested loaders. Note this `data` is **not a ref**. This is not meant to be used in `setup()` or `<script setup>`.
|
||||
* ```ts
|
||||
* export const useUserConnections = defineLoader(async () => {
|
||||
* const user = await useUserData()
|
||||
* return fetchUserConnections(user.id)
|
||||
* })
|
||||
* ```
|
||||
*/
|
||||
(): _PromiseMerged<Exclude<TData, NavigationResult | undefined>, UseDataLoaderColadaResult<Exclude<TData, NavigationResult>, ErrorDefault, Exclude<TData, NavigationResult>>>;
|
||||
}
|
||||
interface DataLoaderColadaEntry<TData, TError = unknown, TDataInitial extends TData | undefined = TData | undefined> extends DataLoaderEntryBase<TData, TError, TDataInitial> {
|
||||
/**
|
||||
* Reactive route passed to pinia colada so it automatically refetch
|
||||
*/
|
||||
route: ShallowRef<RouteLocationNormalizedLoaded>;
|
||||
/**
|
||||
* Tracked routes to know when the data should be refreshed. Key is the key of the query.
|
||||
*/
|
||||
tracked: Map<string, TrackedRoute>;
|
||||
/**
|
||||
* Extended options for pinia colada
|
||||
*/
|
||||
ext: UseQueryReturn<TData, TError, TDataInitial> | null;
|
||||
}
|
||||
interface TrackedRoute {
|
||||
ready: boolean;
|
||||
params: Partial<LocationQuery>;
|
||||
query: Partial<LocationQuery>;
|
||||
hash: {
|
||||
v: string | null;
|
||||
};
|
||||
}
|
||||
//#endregion
|
||||
export { type DataColadaLoaderContext, type DataLoaderColadaEntry, type DefineDataColadaLoaderOptions, type DefineDataColadaLoaderOptions_DefinedData, type DefineDataColadaLoaderOptions_LaxData, type UseDataLoaderColadaResult, type UseDataLoaderColada_DefinedData, type UseDataLoaderColada_LaxData, type _DefineDataColadaLoaderOptions_Common, defineColadaLoader };
|
||||
+234
@@ -0,0 +1,234 @@
|
||||
/*!
|
||||
* vue-router v5.0.6
|
||||
* (c) 2026 Eduardo San Martin Morote
|
||||
* @license MIT
|
||||
*/
|
||||
import { n as useRouter, t as useRoute } from "../useApi-DtucMc-S.js";
|
||||
import { a as assign, b as toLazyValue, c as setCurrentContext, d as ABORT_CONTROLLER_KEY, f as APP_KEY, g as LOADER_ENTRIES_KEY, h as IS_USE_DATA_LOADER_KEY, l as trackRoute, m as IS_SSR_KEY, n as NavigationResult, o as getCurrentContext, p as DATA_LOADERS_EFFECT_SCOPE_KEY, s as isSubsetOf, v as PENDING_LOCATION_KEY, y as STAGED_NO_VALUE } from "../navigation-guard-DK6v16ki.js";
|
||||
import { shallowRef, watch } from "vue";
|
||||
import { defineQuery, useQuery, useQueryCache } from "@pinia/colada";
|
||||
|
||||
//#region src/experimental/data-loaders/defineColadaLoader.ts
|
||||
function defineColadaLoader(nameOrOptions, _options) {
|
||||
_options = _options || nameOrOptions;
|
||||
const loader = _options.query;
|
||||
const options = {
|
||||
...DEFAULT_DEFINE_LOADER_OPTIONS,
|
||||
..._options,
|
||||
commit: _options?.commit || "after-load"
|
||||
};
|
||||
let isInitial = true;
|
||||
const useDefinedQuery = defineQuery(() => {
|
||||
const router = useRouter();
|
||||
const entry = router[LOADER_ENTRIES_KEY].get(loader);
|
||||
return useQuery({
|
||||
...options,
|
||||
query: () => {
|
||||
const route = entry.route.value;
|
||||
const [trackedRoute, params, query, hash] = trackRoute(route);
|
||||
entry.tracked.set(joinKeys(serializeQueryKey(options.key, trackedRoute)), {
|
||||
ready: false,
|
||||
params,
|
||||
query,
|
||||
hash
|
||||
});
|
||||
return router[APP_KEY].runWithContext(() => loader(trackedRoute, { signal: route.meta[ABORT_CONTROLLER_KEY]?.signal }));
|
||||
},
|
||||
key: () => toValueWithParameters(options.key, entry.route.value)
|
||||
});
|
||||
});
|
||||
function load(to, router, from, parent, reload) {
|
||||
const entries = router[LOADER_ENTRIES_KEY];
|
||||
const isSSR = router[IS_SSR_KEY];
|
||||
const key = serializeQueryKey(options.key, to);
|
||||
if (!entries.has(loader)) {
|
||||
const route = shallowRef(to);
|
||||
entries.set(loader, {
|
||||
data: shallowRef(),
|
||||
isLoading: shallowRef(false),
|
||||
error: shallowRef(null),
|
||||
to,
|
||||
options,
|
||||
children: /* @__PURE__ */ new Set(),
|
||||
resetPending() {
|
||||
this.pendingTo = null;
|
||||
this.pendingLoad = null;
|
||||
this.isLoading.value = false;
|
||||
},
|
||||
staged: STAGED_NO_VALUE,
|
||||
stagedError: null,
|
||||
commit,
|
||||
tracked: /* @__PURE__ */ new Map(),
|
||||
ext: null,
|
||||
route,
|
||||
pendingTo: null,
|
||||
pendingLoad: null
|
||||
});
|
||||
}
|
||||
const entry = entries.get(loader);
|
||||
if (entry.pendingTo === to && entry.pendingLoad) return entry.pendingLoad;
|
||||
const currentContext = getCurrentContext();
|
||||
if (process.env.NODE_ENV !== "production") {
|
||||
if (parent !== currentContext[0]) console.warn(`❌👶 "${key}" has a different parent than the current context. This shouldn't be happening. Please report a bug with a reproduction to https://github.com/vuejs/router/`);
|
||||
}
|
||||
setCurrentContext([
|
||||
entry,
|
||||
router,
|
||||
to
|
||||
]);
|
||||
if (!entry.ext) {
|
||||
entry.ext = useDefinedQuery();
|
||||
useQueryCache().get(toValueWithParameters(options.key, to))?.deps.delete(router[DATA_LOADERS_EFFECT_SCOPE_KEY]);
|
||||
reload = false;
|
||||
}
|
||||
const { isLoading, data, error, ext } = entry;
|
||||
if (isInitial) {
|
||||
isInitial = false;
|
||||
if (ext.data.value !== void 0) {
|
||||
data.value = ext.data.value;
|
||||
setCurrentContext(currentContext);
|
||||
return entry.pendingLoad = Promise.resolve();
|
||||
}
|
||||
}
|
||||
if (entry.route.value !== to) {
|
||||
const tracked = entry.tracked.get(joinKeys(key));
|
||||
reload = !tracked || hasRouteChanged(to, tracked);
|
||||
}
|
||||
entry.route.value = entry.pendingTo = to;
|
||||
isLoading.value = true;
|
||||
entry.staged = STAGED_NO_VALUE;
|
||||
entry.stagedError = error.value;
|
||||
const currentLoad = ext[reload ? "refetch" : "refresh"]().then(() => {
|
||||
if (entry.pendingLoad === currentLoad) {
|
||||
const newError = ext.error.value;
|
||||
if (newError) {
|
||||
if (newError instanceof NavigationResult) {
|
||||
entry.pendingTo = null;
|
||||
throw newError;
|
||||
}
|
||||
entry.stagedError = newError;
|
||||
if (!toLazyValue(options.lazy, to, from) || isSSR) throw newError;
|
||||
} else {
|
||||
const newData = ext.data.value;
|
||||
if (newData instanceof NavigationResult) {
|
||||
if (process.env.NODE_ENV !== "production") console.warn("[vue-router]: Returning a NavigationResult is deprecated. Use reroute() instead, which throws internally.");
|
||||
entry.pendingTo = null;
|
||||
throw newData;
|
||||
} else {
|
||||
entry.staged = newData;
|
||||
entry.stagedError = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}).finally(() => {
|
||||
setCurrentContext(currentContext);
|
||||
if (entry.pendingLoad === currentLoad) {
|
||||
isLoading.value = false;
|
||||
if (options.commit === "immediate" || !router[PENDING_LOCATION_KEY]) entry.commit(to);
|
||||
}
|
||||
});
|
||||
setCurrentContext(currentContext);
|
||||
entry.pendingLoad = currentLoad;
|
||||
return currentLoad;
|
||||
}
|
||||
function commit(to) {
|
||||
const key = serializeQueryKey(options.key, to);
|
||||
if (this.pendingTo === to) {
|
||||
if (process.env.NODE_ENV !== "production") {
|
||||
if (this.staged === STAGED_NO_VALUE && this.stagedError === null) console.warn(`Loader "${key}"'s "commit()" was called but there is no staged data.`);
|
||||
}
|
||||
if (this.staged !== STAGED_NO_VALUE) {
|
||||
this.data.value = this.staged;
|
||||
if (process.env.NODE_ENV !== "production" && !this.tracked.has(joinKeys(key))) {
|
||||
console.warn(`A query was defined with the same key as the loader "[${key.join(", ")}]". If the "key" is meant to be the same, you should directly use the data loader instead. If not, change the key of the "useQuery()".\nSee https://pinia-colada.esm.dev/#TODO`);
|
||||
return;
|
||||
}
|
||||
this.tracked.get(joinKeys(key)).ready = true;
|
||||
}
|
||||
this.error.value = this.stagedError;
|
||||
this.staged = STAGED_NO_VALUE;
|
||||
this.stagedError = this.error.value;
|
||||
this.to = to;
|
||||
this.pendingTo = null;
|
||||
for (const childEntry of this.children) childEntry.commit(to);
|
||||
}
|
||||
}
|
||||
const useDataLoader = () => {
|
||||
const currentContext = getCurrentContext();
|
||||
const [parentEntry, _router, _route] = currentContext;
|
||||
const router = _router || useRouter();
|
||||
const route = _route || useRoute();
|
||||
const app = router[APP_KEY];
|
||||
const entries = router[LOADER_ENTRIES_KEY];
|
||||
let entry = entries.get(loader);
|
||||
if (!entry || parentEntry && entry.pendingTo !== route || !entry.pendingLoad) app.runWithContext(() => load(route, router, void 0, parentEntry, true));
|
||||
entry = entries.get(loader);
|
||||
if (parentEntry) {
|
||||
if (parentEntry !== entry) parentEntry.children.add(entry);
|
||||
}
|
||||
const { data, error, isLoading } = entry;
|
||||
const ext = useDefinedQuery();
|
||||
useQueryCache().get(toValueWithParameters(options.key, route))?.deps.delete(router[DATA_LOADERS_EFFECT_SCOPE_KEY]);
|
||||
watch(ext.data, (newData) => {
|
||||
if (!router[PENDING_LOCATION_KEY]) data.value = newData;
|
||||
});
|
||||
watch(ext.isLoading, (isFetching) => {
|
||||
if (!router[PENDING_LOCATION_KEY]) isLoading.value = isFetching;
|
||||
});
|
||||
watch(ext.error, (newError) => {
|
||||
if (!router[PENDING_LOCATION_KEY]) error.value = newError;
|
||||
});
|
||||
const useDataLoaderResult = {
|
||||
data,
|
||||
error,
|
||||
isLoading,
|
||||
reload: (to = router.currentRoute.value) => app.runWithContext(() => load(to, router, void 0, void 0, true)).then(() => entry.commit(to)),
|
||||
refetch: (to = router.currentRoute.value) => app.runWithContext(() => load(to, router, void 0, void 0, true)).then(() => (entry.commit(to), entry.ext.state.value)),
|
||||
refresh: (to = router.currentRoute.value) => app.runWithContext(() => load(to, router)).then(() => (entry.commit(to), entry.ext.state.value)),
|
||||
status: ext.status,
|
||||
asyncStatus: ext.asyncStatus,
|
||||
state: ext.state,
|
||||
isPending: ext.isPending
|
||||
};
|
||||
const promise = entry.pendingLoad.then(() => entry.staged === STAGED_NO_VALUE ? ext.data.value : entry.staged).catch((e) => parentEntry ? Promise.reject(e) : null).finally(() => setCurrentContext(currentContext));
|
||||
setCurrentContext(currentContext);
|
||||
return assign(promise, useDataLoaderResult);
|
||||
};
|
||||
useDataLoader[IS_USE_DATA_LOADER_KEY] = true;
|
||||
useDataLoader._ = {
|
||||
load,
|
||||
options,
|
||||
getEntry(router) {
|
||||
return router[LOADER_ENTRIES_KEY].get(loader);
|
||||
}
|
||||
};
|
||||
return useDataLoader;
|
||||
}
|
||||
const joinKeys = (keys) => keys.join("|");
|
||||
function hasRouteChanged(to, tracked) {
|
||||
return !tracked.ready || !isSubsetOf(tracked.params, to.params) || !isSubsetOf(tracked.query, to.query) || tracked.hash.v != null && tracked.hash.v !== to.hash;
|
||||
}
|
||||
const DEFAULT_DEFINE_LOADER_OPTIONS = {
|
||||
lazy: false,
|
||||
server: true,
|
||||
commit: "after-load"
|
||||
};
|
||||
const toValueWithParameters = (optionValue, arg) => {
|
||||
return typeof optionValue === "function" ? optionValue(arg) : optionValue;
|
||||
};
|
||||
/**
|
||||
* Transform the key to a string array so it can be used as a key in caches.
|
||||
*
|
||||
* @param key - key to transform
|
||||
* @param to - route to use
|
||||
*/
|
||||
function serializeQueryKey(keyOption, to) {
|
||||
const key = toValueWithParameters(keyOption, to);
|
||||
return (Array.isArray(key) ? key : [key]).map(stringifyFlatObject);
|
||||
}
|
||||
function stringifyFlatObject(obj) {
|
||||
return obj && typeof obj === "object" ? JSON.stringify(obj, Object.keys(obj).sort()) : String(obj);
|
||||
}
|
||||
|
||||
//#endregion
|
||||
export { defineColadaLoader };
|
||||
Reference in New Issue
Block a user