@tanstack/react-router 1.87.12 → 1.89.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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tanstack/react-router",
3
- "version": "1.87.12",
3
+ "version": "1.89.0",
4
4
  "description": "Modern and scalable routing for React applications",
5
5
  "author": "Tanner Linsley",
6
6
  "license": "MIT",
@@ -15,6 +15,7 @@ import type {
15
15
  export interface CommitLocationOptions {
16
16
  replace?: boolean
17
17
  resetScroll?: boolean
18
+ hashScrollIntoView?: boolean | ScrollIntoViewOptions
18
19
  viewTransition?: boolean | ViewTransitionOptions
19
20
  /**
20
21
  * @deprecated All navigations use React transitions under the hood now
@@ -38,7 +39,7 @@ export type NavigateFn = <
38
39
  TMaskTo extends string = '',
39
40
  >(
40
41
  opts: NavigateOptions<TRouter, TFrom, TTo, TMaskFrom, TMaskTo>,
41
- ) => Promise<void>
42
+ ) => Promise<void> | void
42
43
 
43
44
  export type BuildLocationFn = <
44
45
  TRouter extends RegisteredRouter,
@@ -143,10 +143,13 @@ export function Transitioner() {
143
143
  }))
144
144
 
145
145
  if (typeof document !== 'undefined' && (document as any).querySelector) {
146
- if (router.state.location.hash !== '') {
146
+ const hashScrollIntoViewOptions =
147
+ router.state.location.state.__hashScrollIntoViewOptions ?? true
148
+
149
+ if (hashScrollIntoViewOptions && router.state.location.hash !== '') {
147
150
  const el = document.getElementById(router.state.location.hash)
148
151
  if (el) {
149
- el.scrollIntoView()
152
+ el.scrollIntoView(hashScrollIntoViewOptions)
150
153
  }
151
154
  }
152
155
  }
package/src/history.ts CHANGED
@@ -4,5 +4,6 @@ declare module '@tanstack/history' {
4
4
  interface HistoryState {
5
5
  __tempLocation?: HistoryLocation
6
6
  __tempKey?: string
7
+ __hashScrollIntoViewOptions?: boolean | ScrollIntoViewOptions
7
8
  }
8
9
  }
package/src/link.tsx CHANGED
@@ -210,6 +210,10 @@ export type NavigateOptions<
210
210
  > = ToOptions<TRouter, TFrom, TTo, TMaskFrom, TMaskTo> & NavigateOptionProps
211
211
 
212
212
  export interface NavigateOptionProps {
213
+ // if set to `true`, the router will scroll the element with an id matching the hash into view with default ScrollIntoViewOptions.
214
+ // if set to `false`, the router will not scroll the element with an id matching the hash into view.
215
+ // if set to `ScrollIntoViewOptions`, the router will scroll the element with an id matching the hash into view with the provided options.
216
+ hashScrollIntoView?: boolean | ScrollIntoViewOptions
213
217
  // `replace` is a boolean that determines whether the navigation should replace the current history entry or push a new one.
214
218
  replace?: boolean
215
219
  resetScroll?: boolean
@@ -219,6 +223,8 @@ export interface NavigateOptionProps {
219
223
  // if set to `ViewTransitionOptions`, the router will pass the `types` field to document.startViewTransition({update: fn, types: viewTransition.types}) call
220
224
  viewTransition?: boolean | ViewTransitionOptions
221
225
  ignoreBlocker?: boolean
226
+ reloadDocument?: boolean
227
+ href?: string
222
228
  }
223
229
 
224
230
  export type ToOptions<
@@ -607,6 +613,7 @@ export function useLinkProps<
607
613
  to,
608
614
  preload: userPreload,
609
615
  preloadDelay: userPreloadDelay,
616
+ hashScrollIntoView,
610
617
  replace,
611
618
  startTransition,
612
619
  resetScroll,
@@ -643,6 +650,9 @@ export function useLinkProps<
643
650
  // null for LinkUtils
644
651
 
645
652
  const type: 'internal' | 'external' = React.useMemo(() => {
653
+ if (rest.reloadDocument) {
654
+ return 'external'
655
+ }
646
656
  try {
647
657
  new URL(`${to}`)
648
658
  return 'external'
@@ -801,6 +811,7 @@ export function useLinkProps<
801
811
  ...options,
802
812
  replace,
803
813
  resetScroll,
814
+ hashScrollIntoView,
804
815
  startTransition,
805
816
  viewTransition,
806
817
  ignoreBlocker,
package/src/redirects.ts CHANGED
@@ -12,10 +12,10 @@ export type Redirect<
12
12
  TMaskFrom extends RoutePaths<TRouter['routeTree']> | string = TFrom,
13
13
  TMaskTo extends string = '.',
14
14
  > = {
15
+ href?: string
15
16
  /**
16
17
  * @deprecated Use `statusCode` instead
17
18
  **/
18
- href?: string
19
19
  code?: number
20
20
  statusCode?: number
21
21
  throw?: any
@@ -47,6 +47,14 @@ export function redirect<
47
47
  ;(opts as any).isRedirect = true
48
48
  opts.statusCode = opts.statusCode || opts.code || 307
49
49
  opts.headers = opts.headers || {}
50
+ if (!opts.reloadDocument) {
51
+ opts.reloadDocument = false
52
+ try {
53
+ new URL(`${opts.href}`)
54
+ opts.reloadDocument = true
55
+ } catch {}
56
+ }
57
+
50
58
  if (opts.throw) {
51
59
  throw opts
52
60
  }
package/src/route.ts CHANGED
@@ -569,7 +569,7 @@ export interface LoaderFnContext<
569
569
  /**
570
570
  * @deprecated Use `throw redirect({ to: '/somewhere' })` instead
571
571
  **/
572
- navigate: (opts: NavigateOptions<AnyRouter>) => Promise<void>
572
+ navigate: (opts: NavigateOptions<AnyRouter>) => Promise<void> | void
573
573
  // root route does not have a parent match
574
574
  parentMatchPromise: TId extends RootRouteId
575
575
  ? never
package/src/router.ts CHANGED
@@ -313,6 +313,14 @@ export interface RouterOptions<
313
313
  * @link [API Docs](https://tanstack.com/router/latest/docs/framework/react/api/router/RouterOptionsType#defaultviewtransition-property)
314
314
  */
315
315
  defaultViewTransition?: boolean | ViewTransitionOptions
316
+ /**
317
+ * The default `hashScrollIntoView` a route should use if no hashScrollIntoView is provided while navigating
318
+ *
319
+ * See [MDN](https://developer.mozilla.org/en-US/docs/Web/API/Element/scrollIntoView) for more information on `ScrollIntoViewOptions`.
320
+ *
321
+ * @link [API Docs](https://tanstack.com/router/latest/docs/framework/react/api/router/RouterOptionsType#defaulthashscrollintoview-property)
322
+ */
323
+ defaultHashScrollIntoView?: boolean | ScrollIntoViewOptions
316
324
  /**
317
325
  * @default 'fuzzy'
318
326
  * @link [API Docs](https://tanstack.com/router/latest/docs/framework/react/api/router/RouterOptionsType#notfoundmode-property)
@@ -536,6 +544,7 @@ export interface BuildNextOptions {
536
544
  }
537
545
  from?: string
538
546
  _fromLocation?: ParsedLocation
547
+ href?: string
539
548
  }
540
549
 
541
550
  export interface MatchedRoutesResult {
@@ -1808,7 +1817,7 @@ export class Router<
1808
1817
  this.load()
1809
1818
  } else {
1810
1819
  // eslint-disable-next-line prefer-const
1811
- let { maskedLocation, ...nextHistory } = next
1820
+ let { maskedLocation, hashScrollIntoView, ...nextHistory } = next
1812
1821
 
1813
1822
  if (maskedLocation) {
1814
1823
  nextHistory = {
@@ -1838,6 +1847,9 @@ export class Router<
1838
1847
  }
1839
1848
  }
1840
1849
 
1850
+ nextHistory.state.__hashScrollIntoViewOptions =
1851
+ hashScrollIntoView ?? this.options.defaultHashScrollIntoView ?? true
1852
+
1841
1853
  this.shouldViewTransition = viewTransition
1842
1854
 
1843
1855
  this.history[next.replace ? 'replace' : 'push'](
@@ -1859,11 +1871,12 @@ export class Router<
1859
1871
  buildAndCommitLocation = ({
1860
1872
  replace,
1861
1873
  resetScroll,
1874
+ hashScrollIntoView,
1862
1875
  viewTransition,
1863
1876
  ignoreBlocker,
1877
+ href,
1864
1878
  ...rest
1865
1879
  }: BuildNextOptions & CommitLocationOptions = {}) => {
1866
- const href = (rest as any).href
1867
1880
  if (href) {
1868
1881
  const parsed = parseHref(href, {})
1869
1882
  rest.to = parsed.pathname
@@ -1881,33 +1894,29 @@ export class Router<
1881
1894
  viewTransition,
1882
1895
  replace,
1883
1896
  resetScroll,
1897
+ hashScrollIntoView,
1884
1898
  ignoreBlocker,
1885
1899
  })
1886
1900
  }
1887
1901
 
1888
- navigate: NavigateFn = ({ to, ...rest }) => {
1889
- // If this link simply reloads the current route,
1890
- // make sure it has a new key so it will trigger a data refresh
1891
-
1892
- // If this `to` is a valid external URL, return
1893
- // null for LinkUtils
1894
- const toString = String(to)
1895
- let isExternal
1896
-
1897
- try {
1898
- new URL(`${toString}`)
1899
- isExternal = true
1900
- } catch (e) {}
1901
-
1902
- invariant(
1903
- !isExternal,
1904
- 'Attempting to navigate to external url with router.navigate!',
1905
- )
1902
+ navigate: NavigateFn = ({ to, reloadDocument, href, ...rest }) => {
1903
+ if (reloadDocument) {
1904
+ if (!href) {
1905
+ const location = this.buildLocation({ to, ...rest } as any)
1906
+ href = location.href
1907
+ }
1908
+ if (rest.replace) {
1909
+ window.location.replace(href)
1910
+ } else {
1911
+ window.location.href = href
1912
+ }
1913
+ return
1914
+ }
1906
1915
 
1907
1916
  return this.buildAndCommitLocation({
1908
1917
  ...rest,
1918
+ href,
1909
1919
  to: to as string,
1910
- // to: toString,
1911
1920
  })
1912
1921
  }
1913
1922
 
@@ -2041,7 +2050,7 @@ export class Router<
2041
2050
  redirect = err
2042
2051
  if (!this.isServer) {
2043
2052
  this.navigate({
2044
- ...err,
2053
+ ...redirect,
2045
2054
  replace: true,
2046
2055
  ignoreBlocker: true,
2047
2056
  })
@@ -2194,7 +2203,11 @@ export class Router<
2194
2203
  }
2195
2204
 
2196
2205
  const handleRedirectAndNotFound = (match: AnyRouteMatch, err: any) => {
2197
- if (isResolvedRedirect(err)) throw err
2206
+ if (isResolvedRedirect(err)) {
2207
+ if (!err.reloadDocument) {
2208
+ throw err
2209
+ }
2210
+ }
2198
2211
 
2199
2212
  if (isRedirect(err) || isNotFound(err)) {
2200
2213
  updateMatch(match.id, (prev) => ({
@@ -2877,6 +2890,9 @@ export class Router<
2877
2890
  return matches
2878
2891
  } catch (err) {
2879
2892
  if (isRedirect(err)) {
2893
+ if (err.reloadDocument) {
2894
+ return undefined
2895
+ }
2880
2896
  return await this.preloadRoute({
2881
2897
  ...(err as any),
2882
2898
  _fromLocation: next,