@tanstack/react-router 1.98.0 → 1.98.3

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.
Files changed (48) hide show
  1. package/dist/cjs/Match.cjs +60 -22
  2. package/dist/cjs/Match.cjs.map +1 -1
  3. package/dist/cjs/Matches.cjs +4 -1
  4. package/dist/cjs/Matches.cjs.map +1 -1
  5. package/dist/cjs/ScriptOnce.cjs +1 -1
  6. package/dist/cjs/ScriptOnce.cjs.map +1 -1
  7. package/dist/cjs/ScrollRestoration.cjs +39 -0
  8. package/dist/cjs/ScrollRestoration.cjs.map +1 -0
  9. package/dist/cjs/ScrollRestoration.d.cts +15 -0
  10. package/dist/cjs/Transitioner.cjs +3 -33
  11. package/dist/cjs/Transitioner.cjs.map +1 -1
  12. package/dist/cjs/index.cjs +3 -4
  13. package/dist/cjs/index.cjs.map +1 -1
  14. package/dist/cjs/index.d.cts +1 -2
  15. package/dist/cjs/router.cjs +14 -12
  16. package/dist/cjs/router.cjs.map +1 -1
  17. package/dist/cjs/router.d.cts +26 -26
  18. package/dist/cjs/scroll-restoration.cjs +166 -165
  19. package/dist/cjs/scroll-restoration.cjs.map +1 -1
  20. package/dist/cjs/scroll-restoration.d.cts +25 -15
  21. package/dist/esm/Match.js +62 -24
  22. package/dist/esm/Match.js.map +1 -1
  23. package/dist/esm/Matches.js +4 -1
  24. package/dist/esm/Matches.js.map +1 -1
  25. package/dist/esm/ScriptOnce.js +1 -1
  26. package/dist/esm/ScriptOnce.js.map +1 -1
  27. package/dist/esm/ScrollRestoration.d.ts +15 -0
  28. package/dist/esm/ScrollRestoration.js +39 -0
  29. package/dist/esm/ScrollRestoration.js.map +1 -0
  30. package/dist/esm/Transitioner.js +4 -34
  31. package/dist/esm/Transitioner.js.map +1 -1
  32. package/dist/esm/index.d.ts +1 -2
  33. package/dist/esm/index.js +1 -2
  34. package/dist/esm/router.d.ts +26 -26
  35. package/dist/esm/router.js +15 -13
  36. package/dist/esm/router.js.map +1 -1
  37. package/dist/esm/scroll-restoration.d.ts +25 -15
  38. package/dist/esm/scroll-restoration.js +166 -148
  39. package/dist/esm/scroll-restoration.js.map +1 -1
  40. package/package.json +3 -3
  41. package/src/Match.tsx +79 -48
  42. package/src/Matches.tsx +1 -1
  43. package/src/ScriptOnce.tsx +1 -1
  44. package/src/ScrollRestoration.tsx +65 -0
  45. package/src/Transitioner.tsx +4 -40
  46. package/src/index.tsx +1 -3
  47. package/src/router.ts +43 -38
  48. package/src/scroll-restoration.tsx +268 -182
@@ -0,0 +1,65 @@
1
+ import { useRouter } from './useRouter'
2
+ import {
3
+ defaultGetScrollRestorationKey,
4
+ getCssSelector,
5
+ scrollRestorationCache,
6
+ setupScrollRestoration,
7
+ } from './scroll-restoration'
8
+ import type { ScrollRestorationOptions } from './scroll-restoration'
9
+ import type { ParsedLocation } from '@tanstack/router-core'
10
+
11
+ function useScrollRestoration() {
12
+ const router = useRouter()
13
+ setupScrollRestoration(router, true)
14
+ }
15
+
16
+ /**
17
+ * @deprecated use createRouter's `scrollRestoration` option instead
18
+ */
19
+ export function ScrollRestoration(_props: ScrollRestorationOptions) {
20
+ useScrollRestoration()
21
+
22
+ if (process.env.NODE_ENV === 'development') {
23
+ console.warn(
24
+ "The ScrollRestoration component is deprecated. Use createRouter's `scrollRestoration` option instead.",
25
+ )
26
+ }
27
+
28
+ return null
29
+ }
30
+
31
+ export function useElementScrollRestoration(
32
+ options: (
33
+ | {
34
+ id: string
35
+ getElement?: () => Element | undefined | null
36
+ }
37
+ | {
38
+ id?: string
39
+ getElement: () => Element | undefined | null
40
+ }
41
+ ) & {
42
+ getKey?: (location: ParsedLocation) => string
43
+ },
44
+ ) {
45
+ useScrollRestoration()
46
+
47
+ const router = useRouter()
48
+ const getKey = options.getKey || defaultGetScrollRestorationKey
49
+
50
+ let elementSelector = ''
51
+
52
+ if (options.id) {
53
+ elementSelector = `[data-scroll-restoration-id="${options.id}"]`
54
+ } else {
55
+ const element = options.getElement?.()
56
+ if (!element) {
57
+ return
58
+ }
59
+ elementSelector = getCssSelector(element)
60
+ }
61
+
62
+ const restoreKey = getKey(router.latestLocation)
63
+ const byKey = scrollRestorationCache.state[restoreKey]
64
+ return byKey?.[elementSelector]
65
+ }
@@ -1,5 +1,5 @@
1
1
  import * as React from 'react'
2
- import { trimPathRight } from '@tanstack/router-core'
2
+ import { getLocationChangeInfo, trimPathRight } from '@tanstack/router-core'
3
3
  import { useLayoutEffect, usePrevious } from './utils'
4
4
  import { useRouter } from './useRouter'
5
5
  import { useRouterState } from './useRouterState'
@@ -87,17 +87,9 @@ export function Transitioner() {
87
87
  useLayoutEffect(() => {
88
88
  // The router was loading and now it's not
89
89
  if (previousIsLoading && !isLoading) {
90
- const toLocation = router.state.location
91
- const fromLocation = router.state.resolvedLocation
92
- const pathChanged = fromLocation.pathname !== toLocation.pathname
93
- const hrefChanged = fromLocation.href !== toLocation.href
94
-
95
90
  router.emit({
96
91
  type: 'onLoad', // When the new URL has committed, when the new matches have been loaded into state.matches
97
- fromLocation,
98
- toLocation,
99
- pathChanged,
100
- hrefChanged,
92
+ ...getLocationChangeInfo(router.state),
101
93
  })
102
94
  }
103
95
  }, [previousIsLoading, router, isLoading])
@@ -105,17 +97,9 @@ export function Transitioner() {
105
97
  useLayoutEffect(() => {
106
98
  // emit onBeforeRouteMount
107
99
  if (previousIsPagePending && !isPagePending) {
108
- const toLocation = router.state.location
109
- const fromLocation = router.state.resolvedLocation
110
- const pathChanged = fromLocation.pathname !== toLocation.pathname
111
- const hrefChanged = fromLocation.href !== toLocation.href
112
-
113
100
  router.emit({
114
101
  type: 'onBeforeRouteMount',
115
- fromLocation,
116
- toLocation,
117
- pathChanged,
118
- hrefChanged,
102
+ ...getLocationChangeInfo(router.state),
119
103
  })
120
104
  }
121
105
  }, [isPagePending, previousIsPagePending, router])
@@ -123,17 +107,9 @@ export function Transitioner() {
123
107
  useLayoutEffect(() => {
124
108
  // The router was pending and now it's not
125
109
  if (previousIsAnyPending && !isAnyPending) {
126
- const toLocation = router.state.location
127
- const fromLocation = router.state.resolvedLocation
128
- const pathChanged = fromLocation.pathname !== toLocation.pathname
129
- const hrefChanged = fromLocation.href !== toLocation.href
130
-
131
110
  router.emit({
132
111
  type: 'onResolved',
133
- fromLocation,
134
- toLocation,
135
- pathChanged,
136
- hrefChanged,
112
+ ...getLocationChangeInfo(router.state),
137
113
  })
138
114
 
139
115
  router.__store.setState((s) => ({
@@ -141,18 +117,6 @@ export function Transitioner() {
141
117
  status: 'idle',
142
118
  resolvedLocation: s.location,
143
119
  }))
144
-
145
- if (typeof document !== 'undefined' && (document as any).querySelector) {
146
- const hashScrollIntoViewOptions =
147
- router.state.location.state.__hashScrollIntoViewOptions ?? true
148
-
149
- if (hashScrollIntoViewOptions && router.state.location.hash !== '') {
150
- const el = document.getElementById(router.state.location.hash)
151
- if (el) {
152
- el.scrollIntoView(hashScrollIntoViewOptions)
153
- }
154
- }
155
- }
156
120
  }
157
121
  }, [isAnyPending, previousIsAnyPending, router])
158
122
 
package/src/index.tsx CHANGED
@@ -317,11 +317,9 @@ export type {
317
317
  } from './RouterProvider'
318
318
 
319
319
  export {
320
- useScrollRestoration,
321
320
  useElementScrollRestoration,
322
321
  ScrollRestoration,
323
- } from './scroll-restoration'
324
- export type { ScrollRestorationOptions } from './scroll-restoration'
322
+ } from './ScrollRestoration'
325
323
 
326
324
  export type { UseBlockerOpts, ShouldBlockFn } from './useBlocker'
327
325
  export { useBlocker, Block } from './useBlocker'
package/src/router.ts CHANGED
@@ -12,6 +12,7 @@ import {
12
12
  defaultParseSearch,
13
13
  defaultStringifySearch,
14
14
  functionalUpdate,
15
+ getLocationChangeInfo,
15
16
  interpolatePath,
16
17
  joinPaths,
17
18
  last,
@@ -27,6 +28,8 @@ import {
27
28
  } from '@tanstack/router-core'
28
29
  import { isRedirect, isResolvedRedirect } from './redirects'
29
30
  import { isNotFound } from './not-found'
31
+
32
+ import { setupScrollRestoration } from './scroll-restoration'
30
33
  import type * as React from 'react'
31
34
  import type {
32
35
  HistoryLocation,
@@ -454,6 +457,15 @@ export interface RouterOptions<
454
457
  >
455
458
 
456
459
  defaultRemountDeps?: (opts: MakeRemountDepsOptionsUnion<TRouteTree>) => any
460
+
461
+ /**
462
+ * If `false`, scroll restoration will be disabled
463
+ *
464
+ * @default true
465
+ */
466
+ scrollRestoration?: boolean
467
+ getScrollRestorationKey?: (location: ParsedLocation) => string
468
+ scrollRestorationBehavior?: ScrollBehavior
457
469
  }
458
470
 
459
471
  export interface RouterErrorSerializer<TSerializedError> {
@@ -473,7 +485,7 @@ export interface RouterState<
473
485
  pendingMatches?: Array<TRouteMatch>
474
486
  cachedMatches: Array<TRouteMatch>
475
487
  location: ParsedLocation<FullSearchSchema<TRouteTree>>
476
- resolvedLocation: ParsedLocation<FullSearchSchema<TRouteTree>>
488
+ resolvedLocation?: ParsedLocation<FullSearchSchema<TRouteTree>>
477
489
  statusCode: number
478
490
  redirect?: ResolvedRedirect
479
491
  }
@@ -566,46 +578,37 @@ function validateSearch(validateSearch: AnyValidator, input: unknown): unknown {
566
578
  return {}
567
579
  }
568
580
 
581
+ type NavigationEventInfo = {
582
+ fromLocation?: ParsedLocation
583
+ toLocation: ParsedLocation
584
+ pathChanged: boolean
585
+ hrefChanged: boolean
586
+ hashChanged: boolean
587
+ }
588
+
569
589
  export type RouterEvents = {
570
590
  onBeforeNavigate: {
571
591
  type: 'onBeforeNavigate'
572
- fromLocation: ParsedLocation
573
- toLocation: ParsedLocation
574
- pathChanged: boolean
575
- hrefChanged: boolean
576
- }
592
+ } & NavigationEventInfo
577
593
  onBeforeLoad: {
578
594
  type: 'onBeforeLoad'
579
- fromLocation: ParsedLocation
580
- toLocation: ParsedLocation
581
- pathChanged: boolean
582
- hrefChanged: boolean
583
- }
595
+ } & NavigationEventInfo
584
596
  onLoad: {
585
597
  type: 'onLoad'
586
- fromLocation: ParsedLocation
587
- toLocation: ParsedLocation
588
- pathChanged: boolean
589
- hrefChanged: boolean
590
- }
598
+ } & NavigationEventInfo
591
599
  onResolved: {
592
600
  type: 'onResolved'
593
- fromLocation: ParsedLocation
594
- toLocation: ParsedLocation
595
- pathChanged: boolean
596
- hrefChanged: boolean
597
- }
601
+ } & NavigationEventInfo
598
602
  onBeforeRouteMount: {
599
603
  type: 'onBeforeRouteMount'
600
- fromLocation: ParsedLocation
601
- toLocation: ParsedLocation
602
- pathChanged: boolean
603
- hrefChanged: boolean
604
- }
604
+ } & NavigationEventInfo
605
605
  onInjectedHtml: {
606
606
  type: 'onInjectedHtml'
607
607
  promise: Promise<string>
608
608
  }
609
+ onRendered: {
610
+ type: 'onRendered'
611
+ } & NavigationEventInfo
609
612
  }
610
613
 
611
614
  export type RouterEvent = RouterEvents[keyof RouterEvents]
@@ -667,6 +670,8 @@ export class Router<
667
670
  isViewTransitionTypesSupported?: boolean = undefined
668
671
  subscribers = new Set<RouterListener<RouterEvent>>()
669
672
  viewTransitionPromise?: ControlledPromise<true>
673
+ isScrollRestoring = false
674
+ isScrollRestorationSetup = false
670
675
 
671
676
  // Must build in constructor
672
677
  __store!: Store<RouterState<TRouteTree>>
@@ -803,6 +808,8 @@ export class Router<
803
808
  }
804
809
  },
805
810
  })
811
+
812
+ setupScrollRestoration(this)
806
813
  }
807
814
 
808
815
  if (
@@ -1881,8 +1888,6 @@ export class Router<
1881
1888
  try {
1882
1889
  const next = this.latestLocation
1883
1890
  const prevLocation = this.state.resolvedLocation
1884
- const hrefChanged = prevLocation.href !== next.href
1885
- const pathChanged = prevLocation.pathname !== next.pathname
1886
1891
 
1887
1892
  // Cancel any pending matches
1888
1893
  this.cancelMatches()
@@ -1914,19 +1919,19 @@ export class Router<
1914
1919
  if (!this.state.redirect) {
1915
1920
  this.emit({
1916
1921
  type: 'onBeforeNavigate',
1917
- fromLocation: prevLocation,
1918
- toLocation: next,
1919
- pathChanged,
1920
- hrefChanged,
1922
+ ...getLocationChangeInfo({
1923
+ resolvedLocation: prevLocation,
1924
+ location: next,
1925
+ }),
1921
1926
  })
1922
1927
  }
1923
1928
 
1924
1929
  this.emit({
1925
1930
  type: 'onBeforeLoad',
1926
- fromLocation: prevLocation,
1927
- toLocation: next,
1928
- pathChanged,
1929
- hrefChanged,
1931
+ ...getLocationChangeInfo({
1932
+ resolvedLocation: prevLocation,
1933
+ location: next,
1934
+ }),
1930
1935
  })
1931
1936
 
1932
1937
  await this.loadMatches({
@@ -2903,7 +2908,7 @@ export class Router<
2903
2908
 
2904
2909
  const baseLocation = pending
2905
2910
  ? this.latestLocation
2906
- : this.state.resolvedLocation
2911
+ : this.state.resolvedLocation || this.state.location
2907
2912
 
2908
2913
  const match = matchPathname(this.basepath, baseLocation.pathname, {
2909
2914
  ...opts,
@@ -3041,7 +3046,7 @@ export function getInitialRouterState(
3041
3046
  isLoading: false,
3042
3047
  isTransitioning: false,
3043
3048
  status: 'idle',
3044
- resolvedLocation: { ...location },
3049
+ resolvedLocation: undefined,
3045
3050
  location,
3046
3051
  matches: [],
3047
3052
  pendingMatches: [],