@tanstack/react-router 1.65.0 → 1.67.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,2 @@
1
+ import { SearchMiddleware } from './route.js';
2
+ export declare function retainSearchParams<TSearchSchema extends object>(keys: Array<keyof TSearchSchema>): SearchMiddleware<TSearchSchema>;
@@ -0,0 +1,15 @@
1
+ function retainSearchParams(keys) {
2
+ return ({ search, next }) => {
3
+ const result = next(search);
4
+ keys.forEach((key) => {
5
+ if (!(key in result)) {
6
+ result[key] = search[key];
7
+ }
8
+ });
9
+ return result;
10
+ };
11
+ }
12
+ export {
13
+ retainSearchParams
14
+ };
15
+ //# sourceMappingURL=searchMiddleware.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"searchMiddleware.js","sources":["../../src/searchMiddleware.ts"],"sourcesContent":["import type { SearchMiddleware } from './route'\n\nexport function retainSearchParams<TSearchSchema extends object>(\n keys: Array<keyof TSearchSchema>,\n): SearchMiddleware<TSearchSchema> {\n return ({ search, next }) => {\n const result = next(search)\n // add missing keys from search to result\n keys.forEach((key) => {\n if (!(key in result)) {\n result[key] = search[key]\n }\n })\n return result\n }\n}\n"],"names":[],"mappings":"AAEO,SAAS,mBACd,MACiC;AACjC,SAAO,CAAC,EAAE,QAAQ,WAAW;AACrB,UAAA,SAAS,KAAK,MAAM;AAErB,SAAA,QAAQ,CAAC,QAAQ;AAChB,UAAA,EAAE,OAAO,SAAS;AACb,eAAA,GAAG,IAAI,OAAO,GAAG;AAAA,MAC1B;AAAA,IAAA,CACD;AACM,WAAA;AAAA,EAAA;AAEX;"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tanstack/react-router",
3
- "version": "1.65.0",
3
+ "version": "1.67.0",
4
4
  "description": "Modern and scalable routing for React applications",
5
5
  "author": "Tanner Linsley",
6
6
  "license": "MIT",
package/src/index.tsx CHANGED
@@ -351,3 +351,5 @@ export type { Manifest, RouterManagedTag } from './manifest'
351
351
 
352
352
  export { createControlledPromise } from './utils'
353
353
  export type { ControlledPromise } from './utils'
354
+
355
+ export { retainSearchParams } from './searchMiddleware'
package/src/route.ts CHANGED
@@ -338,13 +338,20 @@ export interface UpdatableRouteOptions<
338
338
  preload?: boolean
339
339
  preloadStaleTime?: number
340
340
  preloadGcTime?: number
341
- // Filter functions that can manipulate search params *before* they are passed to links and navigate
342
- // calls that match this route.
341
+ search?: {
342
+ middlewares?: Array<
343
+ SearchMiddleware<ResolveFullSearchSchema<TParentRoute, TSearchValidator>>
344
+ >
345
+ }
346
+ /**
347
+ @deprecated Use search.middlewares instead
348
+ */
343
349
  preSearchFilters?: Array<
344
350
  SearchFilter<ResolveFullSearchSchema<TParentRoute, TSearchValidator>>
345
351
  >
346
- // Filter functions that can manipulate search params *after* they are passed to links and navigate
347
- // calls that match this route.
352
+ /**
353
+ @deprecated Use search.middlewares instead
354
+ */
348
355
  postSearchFilters?: Array<
349
356
  SearchFilter<ResolveFullSearchSchema<TParentRoute, TSearchValidator>>
350
357
  >
@@ -556,6 +563,15 @@ export interface LoaderFnContext<
556
563
 
557
564
  export type SearchFilter<TInput, TResult = TInput> = (prev: TInput) => TResult
558
565
 
566
+ export type SearchMiddlewareContext<TSearchSchema> = {
567
+ search: TSearchSchema
568
+ next: (newSearch: TSearchSchema) => TSearchSchema
569
+ }
570
+
571
+ export type SearchMiddleware<TSearchSchema> = (
572
+ ctx: SearchMiddlewareContext<TSearchSchema>,
573
+ ) => TSearchSchema
574
+
559
575
  export type ResolveId<
560
576
  TParentRoute,
561
577
  TCustomId extends string,
package/src/router.ts CHANGED
@@ -51,6 +51,7 @@ import type {
51
51
  RouteComponent,
52
52
  RouteContextOptions,
53
53
  RouteMask,
54
+ SearchMiddleware,
54
55
  } from './route'
55
56
  import type {
56
57
  FullSearchSchema,
@@ -1428,39 +1429,86 @@ export class Router<
1428
1429
  leaveParams: opts.leaveParams,
1429
1430
  })
1430
1431
 
1431
- const preSearchFilters =
1432
- stayingMatches
1433
- ?.map((route) => route.options.preSearchFilters ?? [])
1434
- .flat()
1435
- .filter(Boolean) ?? []
1436
-
1437
- const postSearchFilters =
1438
- stayingMatches
1439
- ?.map((route) => route.options.postSearchFilters ?? [])
1440
- .flat()
1441
- .filter(Boolean) ?? []
1442
-
1443
- // Pre filters first
1444
- const preFilteredSearch = preSearchFilters.length
1445
- ? preSearchFilters.reduce((prev, next) => next(prev), fromSearch)
1446
- : fromSearch
1447
-
1448
- // Then the link/navigate function
1449
- const destSearch =
1450
- dest.search === true
1451
- ? preFilteredSearch // Preserve resolvedFrom true
1452
- : dest.search
1453
- ? functionalUpdate(dest.search, preFilteredSearch) // Updater
1454
- : preSearchFilters.length
1455
- ? preFilteredSearch // Preserve resolvedFrom filters
1456
- : {}
1457
-
1458
- // Then post filters
1459
- const postFilteredSearch = postSearchFilters.length
1460
- ? postSearchFilters.reduce((prev, next) => next(prev), destSearch)
1461
- : destSearch
1462
-
1463
- let search = postFilteredSearch
1432
+ const applyMiddlewares = () => {
1433
+ const allMiddlewares =
1434
+ stayingMatches?.reduce(
1435
+ (acc, route) => {
1436
+ let middlewares: Array<SearchMiddleware<any>> = []
1437
+ if ('search' in route.options) {
1438
+ if (route.options.search?.middlewares) {
1439
+ middlewares = route.options.search.middlewares
1440
+ }
1441
+ }
1442
+ // TODO remove preSearchFilters and postSearchFilters in v2
1443
+ else if (
1444
+ route.options.preSearchFilters ||
1445
+ route.options.postSearchFilters
1446
+ ) {
1447
+ const legacyMiddleware: SearchMiddleware<any> = ({
1448
+ search,
1449
+ next,
1450
+ }) => {
1451
+ let nextSearch = search
1452
+ if (
1453
+ 'preSearchFilters' in route.options &&
1454
+ route.options.preSearchFilters
1455
+ ) {
1456
+ nextSearch = route.options.preSearchFilters.reduce(
1457
+ (prev, next) => next(prev),
1458
+ search,
1459
+ )
1460
+ }
1461
+ const result = next(nextSearch)
1462
+ if (
1463
+ 'postSearchFilters' in route.options &&
1464
+ route.options.postSearchFilters
1465
+ ) {
1466
+ return route.options.postSearchFilters.reduce(
1467
+ (prev, next) => next(prev),
1468
+ result,
1469
+ )
1470
+ }
1471
+ return result
1472
+ }
1473
+ middlewares = [legacyMiddleware]
1474
+ }
1475
+ return acc.concat(middlewares)
1476
+ },
1477
+ [] as Array<SearchMiddleware<any>>,
1478
+ ) ?? []
1479
+
1480
+ // the chain ends here since `next` is not called
1481
+ const final: SearchMiddleware<any> = ({ search }) => {
1482
+ if (!dest.search) {
1483
+ return {}
1484
+ }
1485
+ if (dest.search === true) {
1486
+ return search
1487
+ }
1488
+ return functionalUpdate(dest.search, search)
1489
+ }
1490
+ allMiddlewares.push(final)
1491
+
1492
+ const applyNext = (index: number, currentSearch: any): any => {
1493
+ // no more middlewares left, return the current search
1494
+ if (index >= allMiddlewares.length) {
1495
+ return currentSearch
1496
+ }
1497
+
1498
+ const middleware = allMiddlewares[index]!
1499
+
1500
+ const next = (newSearch: any): any => {
1501
+ return applyNext(index + 1, newSearch)
1502
+ }
1503
+
1504
+ return middleware({ search: currentSearch, next })
1505
+ }
1506
+
1507
+ // Start applying middlewares
1508
+ return applyNext(0, fromSearch)
1509
+ }
1510
+
1511
+ let search = applyMiddlewares()
1464
1512
 
1465
1513
  if (opts._includeValidateSearch) {
1466
1514
  matchedRoutesResult?.matchedRoutes.forEach((route) => {
@@ -1657,7 +1705,8 @@ export class Router<
1657
1705
  const parsed = parseHref(href, {})
1658
1706
  rest.to = parsed.pathname
1659
1707
  rest.search = this.options.parseSearch(parsed.search)
1660
- rest.hash = parsed.hash
1708
+ // remove the leading `#` from the hash
1709
+ rest.hash = parsed.hash.slice(1)
1661
1710
  }
1662
1711
 
1663
1712
  const location = this.buildLocation({
@@ -0,0 +1,16 @@
1
+ import type { SearchMiddleware } from './route'
2
+
3
+ export function retainSearchParams<TSearchSchema extends object>(
4
+ keys: Array<keyof TSearchSchema>,
5
+ ): SearchMiddleware<TSearchSchema> {
6
+ return ({ search, next }) => {
7
+ const result = next(search)
8
+ // add missing keys from search to result
9
+ keys.forEach((key) => {
10
+ if (!(key in result)) {
11
+ result[key] = search[key]
12
+ }
13
+ })
14
+ return result
15
+ }
16
+ }