index.mjs 28 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753
  1. /*!
  2. * vue-router v4.6.4
  3. * (c) 2025 Eduardo San Martin Morote
  4. * @license MIT
  5. */
  6. import { $ as isBrowser, C as computeScrollPosition, D as scrollToPosition, E as saveScrollPosition, F as isSameRouteLocation, G as encodePath, H as decode, J as assign, K as warn$1, N as NEW_stringifyURL, P as START_LOCATION_NORMALIZED, Q as noop, R as parseURL, T as getScrollKey, W as encodeParam, X as isArray, Y as identityFn, _ as ErrorTypes, b as isNavigationFailure, c as useCallbacks, d as routerKey, f as routerViewLocationKey, g as stringifyQuery, h as parseQuery, i as guardToPromiseFn, k as NavigationType, m as normalizeQuery, n as extractChangingRecords, r as extractComponentsGuards, t as addDevtools, u as routeLocationKey, w as getSavedScrollPosition, y as createRouterError, z as resolveRelativePath } from "../devtools-EWN81iOl.mjs";
  7. import { nextTick, shallowReactive, shallowRef, toValue, unref, warn } from "vue";
  8. //#region src/experimental/router.ts
  9. function normalizeRouteRecord(record) {
  10. const normalizedRecord = {
  11. meta: {},
  12. props: {},
  13. ...record,
  14. instances: {},
  15. leaveGuards: /* @__PURE__ */ new Set(),
  16. updateGuards: /* @__PURE__ */ new Set()
  17. };
  18. Object.defineProperty(normalizedRecord, "mods", { value: {} });
  19. return normalizedRecord;
  20. }
  21. /**
  22. * Merges route record objects for the experimental resolver format.
  23. * This function is specifically designed to work with objects that will be passed to normalizeRouteRecord().
  24. *
  25. * @internal
  26. *
  27. * @param main - main route record object
  28. * @param routeRecords - route records to merge (from definePage imports)
  29. * @returns merged route record object
  30. */
  31. function mergeRouteRecord(main, ...routeRecords) {
  32. for (const record of routeRecords) {
  33. main.meta = {
  34. ...main.meta,
  35. ...record.meta
  36. };
  37. main.props = {
  38. ...main.props,
  39. ...record.props
  40. };
  41. }
  42. return main;
  43. }
  44. /**
  45. * Creates an experimental Router that allows passing a resolver instead of a
  46. * routes array. This router does not have `addRoute()` and `removeRoute()`
  47. * methods and is meant to be used with unplugin-vue-router by generating the
  48. * resolver from the `pages/` folder
  49. *
  50. * @param options - Options to initialize the router
  51. */
  52. function experimental_createRouter(options) {
  53. let { resolver, stringifyQuery: stringifyQuery$1 = stringifyQuery, history: routerHistory } = options;
  54. const beforeGuards = useCallbacks();
  55. const beforeResolveGuards = useCallbacks();
  56. const afterGuards = useCallbacks();
  57. const currentRoute = shallowRef(START_LOCATION_NORMALIZED);
  58. let pendingLocation = START_LOCATION_NORMALIZED;
  59. if (isBrowser && options.scrollBehavior) history.scrollRestoration = "manual";
  60. function resolve(...[to, currentLocation]) {
  61. const matchedRoute = resolver.resolve(to, currentLocation ?? (typeof to === "string" ? currentRoute.value : void 0));
  62. const href = routerHistory.createHref(matchedRoute.fullPath);
  63. if (process.env.NODE_ENV !== "production") {
  64. if (href.startsWith("//")) warn(`Location ${JSON.stringify(to)} resolved to "${href}". A resolved location cannot start with multiple slashes.`);
  65. if (!matchedRoute.matched.length) warn(`No match found for location with path "${to}"`);
  66. }
  67. return assign(matchedRoute, {
  68. redirectedFrom: void 0,
  69. href,
  70. meta: mergeMetaFields(matchedRoute.matched)
  71. });
  72. }
  73. function checkCanceledNavigation(to, from) {
  74. if (pendingLocation !== to) return createRouterError(ErrorTypes.NAVIGATION_CANCELLED, {
  75. from,
  76. to
  77. });
  78. }
  79. const push = (...args) => pushWithRedirect(resolve(...args));
  80. const replace = (...args) => pushWithRedirect(resolve(...args), true);
  81. function handleRedirectRecord(to, from) {
  82. const redirect = to.matched.at(-1)?.redirect;
  83. if (redirect) return resolver.resolve(typeof redirect === "function" ? redirect(to, from) : redirect, from);
  84. }
  85. function pushWithRedirect(to, replace$1, redirectedFrom) {
  86. replace$1 = to.replace ?? replace$1;
  87. pendingLocation = to;
  88. const from = currentRoute.value;
  89. const data = to.state;
  90. const force = to.force;
  91. const shouldRedirect = handleRedirectRecord(to, from);
  92. if (shouldRedirect) return pushWithRedirect({
  93. ...resolve(shouldRedirect, currentRoute.value),
  94. state: typeof shouldRedirect === "object" ? assign({}, data, shouldRedirect.state) : data,
  95. force
  96. }, replace$1, redirectedFrom || to);
  97. const toLocation = to;
  98. toLocation.redirectedFrom = redirectedFrom;
  99. let failure;
  100. if (!force && isSameRouteLocation(stringifyQuery$1, from, to)) {
  101. failure = createRouterError(ErrorTypes.NAVIGATION_DUPLICATED, {
  102. to: toLocation,
  103. from
  104. });
  105. handleScroll(from, from, true, false);
  106. }
  107. 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$1) => {
  108. if (failure$1) {
  109. if (isNavigationFailure(failure$1, ErrorTypes.NAVIGATION_GUARD_REDIRECT)) {
  110. if (process.env.NODE_ENV !== "production" && isSameRouteLocation(stringifyQuery$1, resolve(failure$1.to), toLocation) && redirectedFrom && (redirectedFrom._count = redirectedFrom._count ? redirectedFrom._count + 1 : 1) > 30) {
  111. 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.`);
  112. return Promise.reject(/* @__PURE__ */ new Error("Infinite redirect in navigation guard"));
  113. }
  114. return pushWithRedirect({
  115. ...resolve(failure$1.to, currentRoute.value),
  116. state: typeof failure$1.to === "object" ? assign({}, data, failure$1.to.state) : data,
  117. force
  118. }, replace$1, redirectedFrom || toLocation);
  119. }
  120. } else failure$1 = finalizeNavigation(toLocation, from, true, replace$1, data);
  121. triggerAfterEach(toLocation, from, failure$1);
  122. return failure$1;
  123. });
  124. }
  125. /**
  126. * Helper to reject and skip all navigation guards if a new navigation happened
  127. * @param to
  128. * @param from
  129. */
  130. function checkCanceledNavigationAndReject(to, from) {
  131. const error = checkCanceledNavigation(to, from);
  132. return error ? Promise.reject(error) : Promise.resolve();
  133. }
  134. function runWithContext(fn) {
  135. const app = installedApps.values().next().value;
  136. return app?.runWithContext ? app.runWithContext(fn) : fn();
  137. }
  138. function navigate(to, from) {
  139. let guards;
  140. const [leavingRecords, updatingRecords, enteringRecords] = extractChangingRecords(to, from);
  141. guards = extractComponentsGuards(leavingRecords.reverse(), "beforeRouteLeave", to, from);
  142. for (const record of leavingRecords) record.leaveGuards.forEach((guard) => {
  143. guards.push(guardToPromiseFn(guard, to, from));
  144. });
  145. const canceledNavigationCheck = checkCanceledNavigationAndReject.bind(null, to, from);
  146. guards.push(canceledNavigationCheck);
  147. return runGuardQueue(guards).then(() => {
  148. guards = [];
  149. for (const guard of beforeGuards.list()) guards.push(guardToPromiseFn(guard, to, from));
  150. guards.push(canceledNavigationCheck);
  151. return runGuardQueue(guards);
  152. }).then(() => {
  153. guards = extractComponentsGuards(updatingRecords, "beforeRouteUpdate", to, from);
  154. for (const record of updatingRecords) record.updateGuards.forEach((guard) => {
  155. guards.push(guardToPromiseFn(guard, to, from));
  156. });
  157. guards.push(canceledNavigationCheck);
  158. return runGuardQueue(guards);
  159. }).then(() => {
  160. guards = [];
  161. for (const record of enteringRecords) if (record.beforeEnter) if (isArray(record.beforeEnter)) for (const beforeEnter of record.beforeEnter) guards.push(guardToPromiseFn(beforeEnter, to, from));
  162. else guards.push(guardToPromiseFn(record.beforeEnter, to, from));
  163. guards.push(canceledNavigationCheck);
  164. return runGuardQueue(guards);
  165. }).then(() => {
  166. to.matched.forEach((record) => record.enterCallbacks = {});
  167. guards = extractComponentsGuards(enteringRecords, "beforeRouteEnter", to, from, runWithContext);
  168. guards.push(canceledNavigationCheck);
  169. return runGuardQueue(guards);
  170. }).then(() => {
  171. guards = [];
  172. for (const guard of beforeResolveGuards.list()) guards.push(guardToPromiseFn(guard, to, from));
  173. guards.push(canceledNavigationCheck);
  174. return runGuardQueue(guards);
  175. }).catch((err) => isNavigationFailure(err, ErrorTypes.NAVIGATION_CANCELLED) ? err : Promise.reject(err));
  176. }
  177. function triggerAfterEach(to, from, failure) {
  178. afterGuards.list().forEach((guard) => runWithContext(() => guard(to, from, failure)));
  179. }
  180. /**
  181. * - Cleans up any navigation guards
  182. * - Changes the url if necessary
  183. * - Calls the scrollBehavior
  184. */
  185. function finalizeNavigation(toLocation, from, isPush, replace$1, data) {
  186. const error = checkCanceledNavigation(toLocation, from);
  187. if (error) return error;
  188. const isFirstNavigation = from === START_LOCATION_NORMALIZED;
  189. const state = !isBrowser ? {} : history.state;
  190. if (isPush) if (replace$1 || isFirstNavigation) routerHistory.replace(toLocation.fullPath, assign({ scroll: isFirstNavigation && state && state.scroll }, data));
  191. else routerHistory.push(toLocation.fullPath, data);
  192. currentRoute.value = toLocation;
  193. handleScroll(toLocation, from, isPush, isFirstNavigation);
  194. markAsReady();
  195. }
  196. let removeHistoryListener;
  197. function setupListeners() {
  198. if (removeHistoryListener) return;
  199. removeHistoryListener = routerHistory.listen((to, _from, info) => {
  200. if (!router.listening) return;
  201. const toLocation = resolve(to);
  202. const shouldRedirect = handleRedirectRecord(toLocation, router.currentRoute.value);
  203. if (shouldRedirect) {
  204. pushWithRedirect(assign(resolve(shouldRedirect), { force: true }), true, toLocation).catch(noop);
  205. return;
  206. }
  207. pendingLocation = toLocation;
  208. const from = currentRoute.value;
  209. if (isBrowser) saveScrollPosition(getScrollKey(from.fullPath, info.delta), computeScrollPosition());
  210. navigate(toLocation, from).catch((error) => {
  211. if (isNavigationFailure(error, ErrorTypes.NAVIGATION_ABORTED | ErrorTypes.NAVIGATION_CANCELLED)) return error;
  212. if (isNavigationFailure(error, ErrorTypes.NAVIGATION_GUARD_REDIRECT)) {
  213. pushWithRedirect(assign(resolve(error.to), { force: true }), void 0, toLocation).then((failure) => {
  214. if (isNavigationFailure(failure, ErrorTypes.NAVIGATION_ABORTED | ErrorTypes.NAVIGATION_DUPLICATED) && !info.delta && info.type === NavigationType.pop) routerHistory.go(-1, false);
  215. }).catch(noop);
  216. return Promise.reject();
  217. }
  218. if (info.delta) routerHistory.go(-info.delta, false);
  219. return triggerError(error, toLocation, from);
  220. }).then((failure) => {
  221. failure = failure || finalizeNavigation(toLocation, from, false);
  222. if (failure) {
  223. if (info.delta && !isNavigationFailure(failure, ErrorTypes.NAVIGATION_CANCELLED)) routerHistory.go(-info.delta, false);
  224. else if (info.type === NavigationType.pop && isNavigationFailure(failure, ErrorTypes.NAVIGATION_ABORTED | ErrorTypes.NAVIGATION_DUPLICATED)) routerHistory.go(-1, false);
  225. }
  226. triggerAfterEach(toLocation, from, failure);
  227. }).catch(noop);
  228. });
  229. }
  230. let readyHandlers = useCallbacks();
  231. let errorListeners = useCallbacks();
  232. let ready;
  233. /**
  234. * Trigger errorListeners added via onError and throws the error as well
  235. *
  236. * @param error - error to throw
  237. * @param to - location we were navigating to when the error happened
  238. * @param from - location we were navigating from when the error happened
  239. * @returns the error as a rejected promise
  240. */
  241. function triggerError(error, to, from) {
  242. markAsReady(error);
  243. const list = errorListeners.list();
  244. if (list.length) list.forEach((handler) => handler(error, to, from));
  245. else {
  246. if (process.env.NODE_ENV !== "production") warn("uncaught error during route navigation:");
  247. console.error(error);
  248. }
  249. return Promise.reject(error);
  250. }
  251. function isReady() {
  252. if (ready && currentRoute.value !== START_LOCATION_NORMALIZED) return Promise.resolve();
  253. return new Promise((resolve$1, reject) => {
  254. readyHandlers.add([resolve$1, reject]);
  255. });
  256. }
  257. function markAsReady(err) {
  258. if (!ready) {
  259. ready = !err;
  260. setupListeners();
  261. readyHandlers.list().forEach(([resolve$1, reject]) => err ? reject(err) : resolve$1());
  262. readyHandlers.reset();
  263. }
  264. return err;
  265. }
  266. function handleScroll(to, from, isPush, isFirstNavigation) {
  267. const { scrollBehavior } = options;
  268. if (!isBrowser || !scrollBehavior) return Promise.resolve();
  269. const scrollPosition = !isPush && getSavedScrollPosition(getScrollKey(to.fullPath, 0)) || (isFirstNavigation || !isPush) && history.state && history.state.scroll || null;
  270. return nextTick().then(() => scrollBehavior(to, from, scrollPosition)).then((position) => position && scrollToPosition(position)).catch((err) => triggerError(err, to, from));
  271. }
  272. const go = (delta) => routerHistory.go(delta);
  273. let started;
  274. const installedApps = /* @__PURE__ */ new Set();
  275. const router = {
  276. currentRoute,
  277. listening: true,
  278. hasRoute: (name) => !!resolver.getRoute(name),
  279. getRoutes: () => resolver.getRoutes(),
  280. resolve,
  281. options,
  282. push,
  283. replace,
  284. go,
  285. back: () => go(-1),
  286. forward: () => go(1),
  287. beforeEach: beforeGuards.add,
  288. beforeResolve: beforeResolveGuards.add,
  289. afterEach: afterGuards.add,
  290. onError: errorListeners.add,
  291. isReady,
  292. install(app) {
  293. app.config.globalProperties.$router = router;
  294. Object.defineProperty(app.config.globalProperties, "$route", {
  295. enumerable: true,
  296. get: () => unref(currentRoute)
  297. });
  298. if (isBrowser && !started && currentRoute.value === START_LOCATION_NORMALIZED) {
  299. started = true;
  300. push(routerHistory.location).catch((err) => {
  301. if (process.env.NODE_ENV !== "production") warn("Unexpected error on initial navigation:", err);
  302. });
  303. }
  304. const reactiveRoute = {};
  305. for (const key in START_LOCATION_NORMALIZED) Object.defineProperty(reactiveRoute, key, {
  306. get: () => currentRoute.value[key],
  307. enumerable: true
  308. });
  309. app.provide(routerKey, router);
  310. app.provide(routeLocationKey, shallowReactive(reactiveRoute));
  311. app.provide(routerViewLocationKey, currentRoute);
  312. installedApps.add(app);
  313. app.onUnmount(() => {
  314. installedApps.delete(app);
  315. if (installedApps.size < 1) {
  316. pendingLocation = START_LOCATION_NORMALIZED;
  317. removeHistoryListener && removeHistoryListener();
  318. removeHistoryListener = null;
  319. currentRoute.value = START_LOCATION_NORMALIZED;
  320. started = false;
  321. ready = false;
  322. }
  323. });
  324. if ((process.env.NODE_ENV !== "production" || __VUE_PROD_DEVTOOLS__) && isBrowser) addDevtools(app, router, resolver);
  325. }
  326. };
  327. function runGuardQueue(guards) {
  328. return guards.reduce((promise, guard) => promise.then(() => runWithContext(guard)), Promise.resolve());
  329. }
  330. if (process.env.NODE_ENV !== "production") router._hmrReplaceResolver = (newResolver) => {
  331. resolver = newResolver;
  332. };
  333. return router;
  334. }
  335. /**
  336. * Merge meta fields of an array of records
  337. *
  338. * @param matched - array of matched records
  339. */
  340. function mergeMetaFields(matched) {
  341. return assign({}, ...matched.map((r) => r.meta));
  342. }
  343. //#endregion
  344. //#region src/experimental/route-resolver/resolver-abstract.ts
  345. /**
  346. * Common properties for a location that couldn't be matched. This ensures
  347. * having the same name while having a `path`, `query` and `hash` that change.
  348. */
  349. const NO_MATCH_LOCATION = {
  350. name: process.env.NODE_ENV !== "production" ? Symbol("no-match") : Symbol(),
  351. params: {},
  352. matched: []
  353. };
  354. //#endregion
  355. //#region src/experimental/route-resolver/resolver-fixed.ts
  356. /**
  357. * Build the `matched` array of a record that includes all parent records from the root to the current one.
  358. */
  359. function buildMatched(record) {
  360. const matched = [];
  361. let node = record;
  362. while (node) {
  363. matched.unshift(node);
  364. node = node.parent;
  365. }
  366. return matched;
  367. }
  368. /**
  369. * Creates a fixed resolver that must have all records defined at creation
  370. * time.
  371. *
  372. * @template TRecord - extended type of the records
  373. * @param {TRecord[]} records - Ordered array of records that will be used to resolve routes
  374. * @returns a resolver that can be passed to the router
  375. */
  376. function createFixedResolver(records) {
  377. const recordMap = /* @__PURE__ */ new Map();
  378. for (const record of records) recordMap.set(record.name, record);
  379. function validateMatch(record, url) {
  380. const pathParams = record.path.match(url.path);
  381. const hashParams = record.hash?.match(url.hash);
  382. const matched = buildMatched(record);
  383. const queryParams = Object.assign({}, ...matched.flatMap((record$1) => record$1.query?.map((query) => query.match(url.query))));
  384. return [matched, {
  385. ...pathParams,
  386. ...queryParams,
  387. ...hashParams
  388. }];
  389. }
  390. function resolve(...[to, currentLocation]) {
  391. if (typeof to === "object" && (to.name || to.path == null)) {
  392. if (process.env.NODE_ENV !== "production" && to.name == null && currentLocation == null) {
  393. warn$1(`Cannot resolve relative location "${JSON.stringify(to)}"without a "name" or a current location. This will crash in production.`, to);
  394. const query$1 = normalizeQuery(to.query);
  395. const hash$1 = to.hash ?? "";
  396. const path$1 = to.path ?? "/";
  397. return {
  398. ...to,
  399. ...NO_MATCH_LOCATION,
  400. fullPath: NEW_stringifyURL(stringifyQuery, path$1, query$1, hash$1),
  401. path: path$1,
  402. query: query$1,
  403. hash: hash$1
  404. };
  405. }
  406. const name = to.name ?? currentLocation.name;
  407. const record = recordMap.get(name);
  408. if (process.env.NODE_ENV !== "production") {
  409. if (!record || !name) throw new Error(`Record "${String(name)}" not found`);
  410. if (typeof to === "object" && to.hash && !to.hash.startsWith("#")) warn$1(`A "hash" should always start with the character "#". Replace "${to.hash}" with "#${to.hash}".`);
  411. }
  412. let params = {
  413. ...currentLocation?.params,
  414. ...to.params
  415. };
  416. const path = record.path.build(params);
  417. const hash = record.hash?.build(params) ?? to.hash ?? currentLocation?.hash ?? "";
  418. let matched = buildMatched(record);
  419. const query = Object.assign({
  420. ...currentLocation?.query,
  421. ...normalizeQuery(to.query)
  422. }, ...matched.flatMap((record$1) => record$1.query?.map((query$1) => query$1.build(params))));
  423. const url = {
  424. ...to,
  425. fullPath: NEW_stringifyURL(stringifyQuery, path, query, hash),
  426. path,
  427. hash,
  428. query
  429. };
  430. [matched, params] = validateMatch(record, url);
  431. return {
  432. ...url,
  433. name,
  434. matched,
  435. params
  436. };
  437. } else {
  438. let url;
  439. if (typeof to === "string") url = parseURL(parseQuery, to, currentLocation?.path);
  440. else {
  441. const query = normalizeQuery(to.query);
  442. const path = resolveRelativePath(to.path, currentLocation?.path || "/");
  443. url = {
  444. ...to,
  445. fullPath: NEW_stringifyURL(stringifyQuery, path, query, to.hash),
  446. path,
  447. query,
  448. hash: to.hash || ""
  449. };
  450. }
  451. let record;
  452. let matched;
  453. let parsedParams;
  454. for (record of records) try {
  455. [matched, parsedParams] = validateMatch(record, url);
  456. break;
  457. } catch (e) {}
  458. if (!parsedParams || !matched) return {
  459. ...url,
  460. ...NO_MATCH_LOCATION
  461. };
  462. return {
  463. ...url,
  464. name: record.name,
  465. params: parsedParams,
  466. matched
  467. };
  468. }
  469. }
  470. return {
  471. resolve,
  472. getRoutes: () => records,
  473. getRoute: (name) => recordMap.get(name)
  474. };
  475. }
  476. //#endregion
  477. //#region src/experimental/route-resolver/matchers/errors.ts
  478. /**
  479. * Error throw when a matcher matches by regex but validation fails.
  480. */
  481. var MatchMiss = class extends Error {
  482. name = "MatchMiss";
  483. };
  484. /**
  485. * Helper to create a {@link MatchMiss} error.
  486. * @param args - Arguments to pass to the `MatchMiss` constructor.
  487. *
  488. * @example
  489. * ```ts
  490. * throw miss()
  491. * // in a number param matcher
  492. * throw miss('Number must be finite')
  493. * ```
  494. */
  495. const miss = (...args) => new MatchMiss(...args);
  496. //#endregion
  497. //#region src/experimental/route-resolver/matchers/matcher-pattern.ts
  498. /**
  499. * Allows matching a static path.
  500. *
  501. * @example
  502. * ```ts
  503. * const matcher = new MatcherPatternPathStatic('/team')
  504. * matcher.match('/team') // {}
  505. * matcher.match('/team/123') // throws MatchMiss
  506. * matcher.build() // '/team'
  507. * ```
  508. */
  509. var MatcherPatternPathStatic = class {
  510. /**
  511. * lowercase version of the path to match against.
  512. * This is used to make the matching case insensitive.
  513. */
  514. pathi;
  515. constructor(path) {
  516. this.path = path;
  517. this.pathi = path.toLowerCase();
  518. }
  519. match(path) {
  520. if (path.toLowerCase() !== this.pathi) throw miss();
  521. return {};
  522. }
  523. build() {
  524. return this.path;
  525. }
  526. };
  527. /**
  528. * Regex to remove trailing slashes from a path.
  529. *
  530. * @internal
  531. */
  532. const TRAILING_SLASHES_RE = /\/*$/;
  533. /**
  534. * Handles the `path` part of a URL with dynamic parameters.
  535. */
  536. var MatcherPatternPathDynamic = class {
  537. /**
  538. * Cached keys of the {@link params} object.
  539. */
  540. paramsKeys;
  541. /**
  542. * Creates a new dynamic path matcher.
  543. *
  544. * @param re - regex to match the path against
  545. * @param params - object of param parsers as {@link MatcherPatternPathDynamic_ParamOptions}
  546. * @param pathParts - array of path parts, where strings are static parts, 1 are regular params, and 0 are splat params (not encode slash)
  547. * @param trailingSlash - whether the path should end with a trailing slash, null means "do not care" (for trailing splat params)
  548. */
  549. constructor(re, params, pathParts, trailingSlash = false) {
  550. this.re = re;
  551. this.params = params;
  552. this.pathParts = pathParts;
  553. this.trailingSlash = trailingSlash;
  554. this.paramsKeys = Object.keys(this.params);
  555. }
  556. match(path) {
  557. if (this.trailingSlash != null && this.trailingSlash === !path.endsWith("/")) throw miss();
  558. const match = path.match(this.re);
  559. if (!match) throw miss();
  560. const params = {};
  561. for (var i = 0; i < this.paramsKeys.length; i++) {
  562. var paramName = this.paramsKeys[i];
  563. var [parser, repeatable] = this.params[paramName];
  564. var currentMatch = match[i + 1] ?? null;
  565. var value = repeatable ? (currentMatch?.split("/") || []).map(decode) : decode(currentMatch);
  566. params[paramName] = (parser?.get || identityFn)(value);
  567. }
  568. if (process.env.NODE_ENV !== "production" && Object.keys(params).length !== Object.keys(this.params).length) warn$1(`Regexp matched ${match.length} params, but ${i} params are defined. Found when matching "${path}" against ${String(this.re)}`);
  569. return params;
  570. }
  571. build(params) {
  572. let paramIndex = 0;
  573. let paramName;
  574. let parser;
  575. let repeatable;
  576. let optional;
  577. let value;
  578. const path = "/" + this.pathParts.map((part) => {
  579. if (typeof part === "string") return part;
  580. else if (typeof part === "number") {
  581. paramName = this.paramsKeys[paramIndex++];
  582. [parser, repeatable, optional] = this.params[paramName];
  583. value = (parser?.set || identityFn)(params[paramName]);
  584. if (Array.isArray(value) && !value.length && !optional) throw miss();
  585. return Array.isArray(value) ? value.map(encodeParam).join("/") : (part ? encodeParam : encodePath)(value);
  586. } else return part.map((subPart) => {
  587. if (typeof subPart === "string") return subPart;
  588. paramName = this.paramsKeys[paramIndex++];
  589. [parser, repeatable, optional] = this.params[paramName];
  590. value = (parser?.set || identityFn)(params[paramName]);
  591. if (process.env.NODE_ENV !== "production" && repeatable) {
  592. warn$1(`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.`);
  593. return Array.isArray(value) ? value.map(encodeParam).join("/") : encodeParam(value);
  594. }
  595. return encodeParam(value);
  596. }).join("");
  597. }).filter(identityFn).join("/");
  598. /**
  599. * If the last part of the path is a splat param and its value is empty, it gets
  600. * filteretd out, resulting in a path that doesn't end with a `/` and doesn't even match
  601. * with the original splat path: e.g. /teams/[...pathMatch] does not match /teams, so it makes
  602. * no sense to build a path it cannot match.
  603. */
  604. return this.trailingSlash == null ? path + (!value && path.at(-1) !== "/" ? "/" : "") : path.replace(TRAILING_SLASHES_RE, this.trailingSlash ? "/" : "");
  605. }
  606. };
  607. //#endregion
  608. //#region src/experimental/route-resolver/matchers/param-parsers/booleans.ts
  609. const PARAM_BOOLEAN_SINGLE = {
  610. get: (value) => {
  611. if (value === void 0) return void 0;
  612. if (value == null) return true;
  613. const lowercaseValue = value.toLowerCase();
  614. if (lowercaseValue === "true") return true;
  615. if (lowercaseValue === "false") return false;
  616. throw miss();
  617. },
  618. set: (value) => value == null ? value : String(value)
  619. };
  620. const PARAM_BOOLEAN_REPEATABLE = {
  621. get: (value) => value.map((v) => {
  622. const result = PARAM_BOOLEAN_SINGLE.get(v);
  623. return result === void 0 ? false : result;
  624. }),
  625. set: (value) => value.map((v) => PARAM_BOOLEAN_SINGLE.set(v))
  626. };
  627. /**
  628. * Native Param parser for booleans.
  629. *
  630. * @internal
  631. */
  632. const PARAM_PARSER_BOOL = {
  633. get: (value) => Array.isArray(value) ? PARAM_BOOLEAN_REPEATABLE.get(value) : PARAM_BOOLEAN_SINGLE.get(value),
  634. set: (value) => Array.isArray(value) ? PARAM_BOOLEAN_REPEATABLE.set(value) : PARAM_BOOLEAN_SINGLE.set(value)
  635. };
  636. //#endregion
  637. //#region src/experimental/route-resolver/matchers/param-parsers/integers.ts
  638. const PARAM_INTEGER_SINGLE = {
  639. get: (value) => {
  640. const num = Number(value);
  641. if (value && Number.isSafeInteger(num)) return num;
  642. throw miss();
  643. },
  644. set: (value) => String(value)
  645. };
  646. const PARAM_INTEGER_REPEATABLE = {
  647. get: (value) => value.filter((v) => v != null).map(PARAM_INTEGER_SINGLE.get),
  648. set: (value) => value.map(PARAM_INTEGER_SINGLE.set)
  649. };
  650. /**
  651. * Native Param parser for integers.
  652. *
  653. * @internal
  654. */
  655. const PARAM_PARSER_INT = {
  656. get: (value) => Array.isArray(value) ? PARAM_INTEGER_REPEATABLE.get(value) : value != null ? PARAM_INTEGER_SINGLE.get(value) : null,
  657. set: (value) => Array.isArray(value) ? PARAM_INTEGER_REPEATABLE.set(value) : value != null ? PARAM_INTEGER_SINGLE.set(value) : null
  658. };
  659. //#endregion
  660. //#region src/experimental/route-resolver/matchers/param-parsers/index.ts
  661. /**
  662. * Default parser for params that will keep values as is, and will use `String()`
  663. */
  664. const PARAM_PARSER_DEFAULTS = {
  665. get: (value) => value ?? null,
  666. set: (value) => value == null ? null : Array.isArray(value) ? value.map((v) => v == null ? null : String(v)) : String(value)
  667. };
  668. /**
  669. * Defines a path param parser.
  670. *
  671. * @param parser - the parser to define. Will be returned as is.
  672. *
  673. * @see {@link defineQueryParamParser}
  674. * @see {@link defineParamParser}
  675. */
  676. /*! #__NO_SIDE_EFFECTS__ */
  677. function definePathParamParser(parser) {
  678. return parser;
  679. }
  680. /**
  681. * Defines a query param parser. Note that query params can also be used as
  682. * path param parsers.
  683. *
  684. * @param parser - the parser to define. Will be returned as is.
  685. *
  686. * @see {@link definePathParamParser}
  687. * @see {@link defineParamParser}
  688. */
  689. /*! #__NO_SIDE_EFFECTS__ */
  690. function defineQueryParamParser(parser) {
  691. return parser;
  692. }
  693. /**
  694. * Alias for {@link defineQueryParamParser}. Implementing a param parser like this
  695. * works for path, query, and hash params.
  696. *
  697. * @see {@link defineQueryParamParser}
  698. * @see {@link definePathParamParser}
  699. */
  700. const defineParamParser = defineQueryParamParser;
  701. //#endregion
  702. //#region src/experimental/route-resolver/matchers/matcher-pattern-query.ts
  703. /**
  704. * Matcher for a specific query parameter. It will read and write the parameter
  705. */
  706. var MatcherPatternQueryParam = class {
  707. constructor(paramName, queryKey, format, parser = {}, defaultValue) {
  708. this.paramName = paramName;
  709. this.queryKey = queryKey;
  710. this.format = format;
  711. this.parser = parser;
  712. this.defaultValue = defaultValue;
  713. }
  714. match(query) {
  715. const queryValue = query[this.queryKey];
  716. let valueBeforeParse = this.format === "value" ? Array.isArray(queryValue) ? queryValue.at(-1) : queryValue : Array.isArray(queryValue) ? queryValue : queryValue == null ? [] : [queryValue];
  717. let value;
  718. if (Array.isArray(valueBeforeParse)) if (queryValue === void 0 && this.defaultValue !== void 0) value = toValue(this.defaultValue);
  719. else try {
  720. value = (this.parser.get ?? PARAM_PARSER_DEFAULTS.get)(valueBeforeParse);
  721. } catch (error) {
  722. if (this.defaultValue === void 0) throw error;
  723. value = void 0;
  724. }
  725. else try {
  726. value = valueBeforeParse === void 0 ? valueBeforeParse : (this.parser.get ?? PARAM_PARSER_DEFAULTS.get)(valueBeforeParse);
  727. } catch (error) {
  728. if (this.defaultValue === void 0) throw error;
  729. }
  730. if (value === void 0) {
  731. if (this.defaultValue === void 0) throw miss();
  732. value = toValue(this.defaultValue);
  733. }
  734. return { [this.paramName]: value };
  735. }
  736. build(params) {
  737. const paramValue = params[this.paramName];
  738. if (paramValue === void 0) return {};
  739. return { [this.queryKey]: (this.parser.set ?? PARAM_PARSER_DEFAULTS.set)(paramValue) };
  740. }
  741. };
  742. //#endregion
  743. export { MatchMiss, MatcherPatternPathDynamic, MatcherPatternPathStatic, MatcherPatternQueryParam, PARAM_PARSER_BOOL, PARAM_PARSER_INT, mergeRouteRecord as _mergeRouteRecord, createFixedResolver, defineParamParser, definePathParamParser, defineQueryParamParser, experimental_createRouter, miss, normalizeRouteRecord };