index.mjs 28 KB

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