@tanstack/router-devtools 0.0.1-beta.279 → 0.0.1-beta.281

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,7 +1,7 @@
1
1
  {
2
2
  "name": "@tanstack/router-devtools",
3
3
  "author": "Tanner Linsley",
4
- "version": "0.0.1-beta.279",
4
+ "version": "0.0.1-beta.281",
5
5
  "license": "MIT",
6
6
  "repository": "tanstack/router",
7
7
  "homepage": "https://tanstack.com/router/",
@@ -41,7 +41,7 @@
41
41
  "dependencies": {
42
42
  "@babel/runtime": "^7.16.7",
43
43
  "date-fns": "^2.29.1",
44
- "@tanstack/react-router": "0.0.1-beta.279"
44
+ "@tanstack/react-router": "0.0.1-beta.281"
45
45
  },
46
46
  "scripts": {
47
47
  "build": "rollup --config rollup.config.js"
package/src/devtools.tsx CHANGED
@@ -8,12 +8,14 @@ import {
8
8
  trimPath,
9
9
  useRouter,
10
10
  useRouterState,
11
+ AnyRouteMatch,
11
12
  } from '@tanstack/react-router'
12
13
 
13
14
  import useLocalStorage from './useLocalStorage'
14
15
  import {
15
16
  getRouteStatusColor,
16
17
  getStatusColor,
18
+ multiSortBy,
17
19
  useIsMounted,
18
20
  useSafeState,
19
21
  } from './utils'
@@ -409,13 +411,13 @@ export function TanStackRouterDevtools({
409
411
  function RouteComp({
410
412
  route,
411
413
  isRoot,
412
- activeRouteId,
413
- setActiveRouteId,
414
+ activeId,
415
+ setActiveId,
414
416
  }: {
415
417
  route: AnyRootRoute | AnyRoute
416
418
  isRoot?: boolean
417
- activeRouteId: string | undefined
418
- setActiveRouteId: (id: string) => void
419
+ activeId: string | undefined
420
+ setActiveId: (id: string) => void
419
421
  }) {
420
422
  const routerState = useRouterState()
421
423
  const matches =
@@ -432,7 +434,7 @@ function RouteComp({
432
434
  aria-label={`Open match details for ${route.id}`}
433
435
  onClick={() => {
434
436
  if (match) {
435
- setActiveRouteId(activeRouteId === route.id ? '' : route.id)
437
+ setActiveId(activeId === route.id ? '' : route.id)
436
438
  }
437
439
  }}
438
440
  style={{
@@ -441,7 +443,9 @@ function RouteComp({
441
443
  cursor: match ? 'pointer' : 'default',
442
444
  alignItems: 'center',
443
445
  background:
444
- route.id === activeRouteId ? 'rgba(255,255,255,.1)' : undefined,
446
+ route.id === activeId ? 'rgba(255,255,255,.1)' : undefined,
447
+ padding: '.25rem .5rem',
448
+ gap: '.5rem',
445
449
  }}
446
450
  >
447
451
  {isRoot ? null : (
@@ -450,7 +454,6 @@ function RouteComp({
450
454
  flex: '0 0 auto',
451
455
  width: '.7rem',
452
456
  height: '.7rem',
453
- margin: '.5rem .75rem',
454
457
  alignItems: 'center',
455
458
  justifyContent: 'center',
456
459
  fontWeight: 'bold',
@@ -461,20 +464,29 @@ function RouteComp({
461
464
  }}
462
465
  />
463
466
  )}
464
- <Code
467
+ <div
465
468
  style={{
466
469
  flex: '1 0 auto',
467
470
  display: 'flex',
468
471
  justifyContent: 'space-between',
469
- padding: '.25rem .5rem .25rem 0',
470
- paddingLeft: isRoot ? '.5rem' : 0,
472
+ alignItems: 'center',
473
+ padding: isRoot ? '0 .25rem' : 0,
471
474
  opacity: match ? 1 : 0.7,
472
475
  fontSize: '0.7rem',
473
476
  }}
474
477
  >
475
- <span>{route.path || trimPath(route.id)} </span>
476
- {match ? <span style={{ opacity: 0.3 }}>{match.id}</span> : null}
477
- </Code>
478
+ <Code>{route.path || trimPath(route.id)} </Code>
479
+ <div
480
+ style={{
481
+ display: 'flex',
482
+ alignItems: 'center',
483
+ gap: '.5rem',
484
+ }}
485
+ >
486
+ {match ? <Code style={{ opacity: 0.3 }}>{match.id}</Code> : null}
487
+ <AgeTicker match={match} />
488
+ </div>
489
+ </div>
478
490
  </div>
479
491
  {(route.children as Route[])?.length ? (
480
492
  <div
@@ -491,8 +503,8 @@ function RouteComp({
491
503
  <RouteComp
492
504
  key={r.id}
493
505
  route={r}
494
- activeRouteId={activeRouteId}
495
- setActiveRouteId={setActiveRouteId}
506
+ activeId={activeId}
507
+ setActiveId={setActiveId}
496
508
  />
497
509
  ))}
498
510
  </div>
@@ -519,6 +531,7 @@ export const TanStackRouterDevtoolsPanel = React.forwardRef<
519
531
  const matches = [
520
532
  ...(routerState.pendingMatches ?? []),
521
533
  ...routerState.matches,
534
+ ...routerState.cachedMatches,
522
535
  ]
523
536
 
524
537
  invariant(
@@ -533,32 +546,22 @@ export const TanStackRouterDevtoolsPanel = React.forwardRef<
533
546
  true,
534
547
  )
535
548
 
536
- const [activeRouteId, setActiveRouteId] = useLocalStorage(
549
+ const [activeId, setActiveId] = useLocalStorage(
537
550
  'tanstackRouterDevtoolsActiveRouteId',
538
551
  '',
539
552
  )
540
553
 
541
554
  const activeMatch = React.useMemo(
542
- () => matches.find((d) => d.routeId === activeRouteId),
543
- [matches, activeRouteId],
555
+ () => matches.find((d) => d.routeId === activeId || d.id === activeId),
556
+ [matches, activeId],
544
557
  )
545
558
 
546
559
  const hasSearch = Object.keys(routerState.location.search || {}).length
547
560
 
548
- // const preloadMatches = matches.filter((match) => {
549
- // return (
550
- // !state.matchIds.includes(match.id) &&
551
- // !state.pendingMatchIds.includes(match.id)
552
- // )
553
- // })
554
-
555
- // React.useEffect(() => {
556
- // const interval = setInterval(() => {
557
- // router.cleanMatches()
558
- // }, 1000)
559
-
560
- // return () => clearInterval(interval)
561
- // }, [router])
561
+ const explorerState = {
562
+ ...router,
563
+ state: router.state,
564
+ }
562
565
 
563
566
  return (
564
567
  <ThemeProvider theme={theme}>
@@ -673,8 +676,43 @@ export const TanStackRouterDevtoolsPanel = React.forwardRef<
673
676
  >
674
677
  <Explorer
675
678
  label="Router"
676
- value={router}
677
- defaultExpanded={{ state: {} as any, context: {} as any }}
679
+ value={Object.fromEntries(
680
+ multiSortBy(
681
+ Object.keys(explorerState),
682
+ (
683
+ [
684
+ 'state',
685
+ 'routesById',
686
+ 'routesByPath',
687
+ 'flatRoutes',
688
+ 'options',
689
+ ] as const
690
+ ).map((d) => (dd) => dd !== d),
691
+ )
692
+ .map((key) => [key, (explorerState as any)[key]])
693
+ .filter(
694
+ (d) =>
695
+ typeof d[1] !== 'function' &&
696
+ ![
697
+ '__store',
698
+ 'basepath',
699
+ 'injectedHtml',
700
+ 'subscribers',
701
+ 'latestLoadPromise',
702
+ 'navigateTimeout',
703
+ 'resetNextScroll',
704
+ 'tempLocationKey',
705
+ 'latestLocation',
706
+ 'routeTree',
707
+ 'history',
708
+ ].includes(d[0]),
709
+ ),
710
+ )}
711
+ defaultExpanded={{
712
+ state: {} as any,
713
+ context: {} as any,
714
+ options: {} as any,
715
+ }}
678
716
  filterSubEntries={(subEntries) => {
679
717
  return subEntries.filter((d) => typeof d.value !== 'function')
680
718
  }}
@@ -761,52 +799,70 @@ export const TanStackRouterDevtoolsPanel = React.forwardRef<
761
799
  zIndex: 1,
762
800
  display: 'flex',
763
801
  alignItems: 'center',
802
+ justifyContent: 'space-between',
764
803
  gap: '.5rem',
765
804
  fontWeight: 'bold',
766
805
  }}
767
806
  >
768
- <button
769
- type="button"
770
- onClick={() => {
771
- setShowMatches(false)
772
- }}
773
- disabled={!showMatches}
807
+ <div
774
808
  style={{
775
- appearance: 'none',
776
- opacity: showMatches ? 0.5 : 1,
777
- border: 0,
778
- background: 'transparent',
779
- color: 'inherit',
780
- cursor: 'pointer',
809
+ display: 'flex',
810
+ alignItems: 'center',
811
+ gap: '.5rem',
781
812
  }}
782
813
  >
783
- Routes
784
- </button>
785
- /
786
- <button
787
- type="button"
788
- onClick={() => {
789
- setShowMatches(true)
790
- }}
791
- disabled={showMatches}
814
+ <button
815
+ type="button"
816
+ onClick={() => {
817
+ setShowMatches(false)
818
+ }}
819
+ disabled={!showMatches}
820
+ style={{
821
+ appearance: 'none',
822
+ opacity: showMatches ? 0.5 : 1,
823
+ border: 0,
824
+ background: 'transparent',
825
+ color: 'inherit',
826
+ cursor: 'pointer',
827
+ }}
828
+ >
829
+ Routes
830
+ </button>
831
+ /
832
+ <button
833
+ type="button"
834
+ onClick={() => {
835
+ setShowMatches(true)
836
+ }}
837
+ disabled={showMatches}
838
+ style={{
839
+ appearance: 'none',
840
+ opacity: !showMatches ? 0.5 : 1,
841
+ border: 0,
842
+ background: 'transparent',
843
+ color: 'inherit',
844
+ cursor: 'pointer',
845
+ }}
846
+ >
847
+ Matches
848
+ </button>
849
+ </div>
850
+ <div
792
851
  style={{
793
- appearance: 'none',
794
- opacity: !showMatches ? 0.5 : 1,
795
- border: 0,
796
- background: 'transparent',
797
- color: 'inherit',
798
- cursor: 'pointer',
852
+ opacity: 0.3,
853
+ fontSize: '0.7rem',
854
+ fontWeight: 'normal',
799
855
  }}
800
856
  >
801
- Matches
802
- </button>
857
+ age / staleTime / gcTime
858
+ </div>
803
859
  </div>
804
860
  {!showMatches ? (
805
861
  <RouteComp
806
862
  route={router.routeTree}
807
863
  isRoot
808
- activeRouteId={activeRouteId}
809
- setActiveRouteId={setActiveRouteId}
864
+ activeId={activeId}
865
+ setActiveId={setActiveId}
810
866
  />
811
867
  ) : (
812
868
  <div>
@@ -816,13 +872,11 @@ export const TanStackRouterDevtoolsPanel = React.forwardRef<
816
872
  ).map((match, i) => {
817
873
  return (
818
874
  <div
819
- key={match.routeId || i}
875
+ key={match.id || i}
820
876
  role="button"
821
- aria-label={`Open match details for ${match.routeId}`}
877
+ aria-label={`Open match details for ${match.id}`}
822
878
  onClick={() =>
823
- setActiveRouteId(
824
- activeRouteId === match.routeId ? '' : match.routeId,
825
- )
879
+ setActiveId(activeId === match.id ? '' : match.id)
826
880
  }
827
881
  style={{
828
882
  display: 'flex',
@@ -858,13 +912,14 @@ export const TanStackRouterDevtoolsPanel = React.forwardRef<
858
912
  >
859
913
  {`${match.id}`}
860
914
  </Code>
915
+ <AgeTicker match={match} />
861
916
  </div>
862
917
  )
863
918
  })}
864
919
  </div>
865
920
  )}
866
921
  </div>
867
- {/* {preloadMatches?.length ? (
922
+ {routerState.cachedMatches?.length ? (
868
923
  <div
869
924
  style={{
870
925
  flex: '1 1 auto',
@@ -881,62 +936,76 @@ export const TanStackRouterDevtoolsPanel = React.forwardRef<
881
936
  zIndex: 1,
882
937
  display: 'flex',
883
938
  alignItems: 'center',
939
+ justifyContent: 'space-between',
884
940
  gap: '.5rem',
885
941
  fontWeight: 'bold',
886
942
  }}
887
943
  >
888
- Preloaded Matches
944
+ <div>Cached Matches</div>
945
+ <div
946
+ style={{
947
+ opacity: 0.3,
948
+ fontSize: '0.7rem',
949
+ fontWeight: 'normal',
950
+ }}
951
+ >
952
+ age / staleTime / gcTime
953
+ </div>
889
954
  </div>
890
- {preloadMatches.map((match) => {
891
- return (
892
- <div
893
- key={match.id}
894
- role="button"
895
- aria-label={`Open match details for ${match.routeId}`}
896
- onClick={() =>
897
- setActiveMatchId(
898
- activeMatchId === match.id ? '' : match.id,
899
- )
900
- }
901
- style={{
902
- display: 'flex',
903
- borderBottom: `solid 1px ${theme.grayAlt}`,
904
- cursor: 'pointer',
905
- alignItems: 'center',
906
- background:
907
- match === activeMatch
908
- ? 'rgba(255,255,255,.1)'
909
- : undefined,
910
- }}
911
- >
955
+ <div>
956
+ {routerState.cachedMatches.map((match) => {
957
+ return (
912
958
  <div
959
+ key={match.id}
960
+ role="button"
961
+ aria-label={`Open match details for ${match.id}`}
962
+ onClick={() =>
963
+ setActiveId(activeId === match.id ? '' : match.id)
964
+ }
913
965
  style={{
914
- flex: '0 0 auto',
915
- width: '1.3rem',
916
- height: '1.3rem',
917
- marginLeft: '.25rem',
918
- background: getStatusColor(match, theme),
966
+ display: 'flex',
967
+ borderBottom: `solid 1px ${theme.grayAlt}`,
968
+ cursor: 'pointer',
919
969
  alignItems: 'center',
920
- justifyContent: 'center',
921
- fontWeight: 'bold',
922
- borderRadius: '.25rem',
923
- transition: 'all .2s ease-out',
924
- }}
925
- />
926
-
927
- <Code
928
- style={{
929
- padding: '.5em',
970
+ background:
971
+ match === activeMatch
972
+ ? 'rgba(255,255,255,.1)'
973
+ : undefined,
930
974
  fontSize: '0.7rem',
931
975
  }}
932
976
  >
933
- {`${match.id}`}
934
- </Code>
935
- </div>
936
- )
937
- })}
977
+ <div
978
+ style={{
979
+ flex: '0 0 auto',
980
+ width: '.75rem',
981
+ height: '.75rem',
982
+ marginLeft: '.25rem',
983
+ background: getStatusColor(match, theme),
984
+ alignItems: 'center',
985
+ justifyContent: 'center',
986
+ fontWeight: 'bold',
987
+ borderRadius: '100%',
988
+ transition: 'all 1s ease-out',
989
+ }}
990
+ />
991
+
992
+ <Code
993
+ style={{
994
+ padding: '.5em',
995
+ }}
996
+ >
997
+ {`${match.id}`}
998
+ </Code>
999
+
1000
+ <div style={{ marginLeft: 'auto' }}>
1001
+ <AgeTicker match={match} />
1002
+ </div>
1003
+ </div>
1004
+ )
1005
+ })}
1006
+ </div>
938
1007
  </div>
939
- ) : null} */}
1008
+ ) : null}
940
1009
  </div>
941
1010
  {activeMatch ? (
942
1011
  <ActivePanel>
@@ -953,7 +1022,11 @@ export const TanStackRouterDevtoolsPanel = React.forwardRef<
953
1022
  Match Details
954
1023
  </div>
955
1024
  <div>
956
- <table>
1025
+ <table
1026
+ style={{
1027
+ fontSize: '0.8rem',
1028
+ }}
1029
+ >
957
1030
  <tbody>
958
1031
  <tr>
959
1032
  <td style={{ opacity: '.5' }}>ID</td>
@@ -969,7 +1042,18 @@ export const TanStackRouterDevtoolsPanel = React.forwardRef<
969
1042
  </tr>
970
1043
  <tr>
971
1044
  <td style={{ opacity: '.5' }}>Status</td>
972
- <td>{activeMatch.status}</td>
1045
+ <td>
1046
+ {routerState.pendingMatches?.find(
1047
+ (d) => d.id === activeMatch.id,
1048
+ )
1049
+ ? 'Pending'
1050
+ : routerState.matches?.find(
1051
+ (d) => d.id === activeMatch.id,
1052
+ )
1053
+ ? 'Active'
1054
+ : 'Cached'}{' '}
1055
+ - {activeMatch.status}
1056
+ </td>
973
1057
  </tr>
974
1058
  {/* <tr>
975
1059
  <td style={{ opacity: '.5' }}>Invalid</td>
@@ -1113,3 +1197,74 @@ export const TanStackRouterDevtoolsPanel = React.forwardRef<
1113
1197
  </ThemeProvider>
1114
1198
  )
1115
1199
  })
1200
+
1201
+ function AgeTicker({ match }: { match?: AnyRouteMatch }) {
1202
+ const router = useRouter()
1203
+
1204
+ const rerender = React.useReducer(
1205
+ () => ({}),
1206
+ () => ({}),
1207
+ )[1]
1208
+
1209
+ React.useEffect(() => {
1210
+ const interval = setInterval(() => {
1211
+ rerender()
1212
+ }, 1000)
1213
+
1214
+ return () => {
1215
+ clearInterval(interval)
1216
+ }
1217
+ }, [])
1218
+
1219
+ if (!match) {
1220
+ return null
1221
+ }
1222
+
1223
+ const route = router.looseRoutesById[match?.routeId]!
1224
+
1225
+ if (!route.options.loader) {
1226
+ return null
1227
+ }
1228
+
1229
+ const age = Date.now() - match?.updatedAt
1230
+ const staleTime =
1231
+ route.options.staleTime ?? router.options.defaultStaleTime ?? 0
1232
+ const gcTime =
1233
+ route.options.gcTime ?? router.options.defaultGcTime ?? 30 * 60 * 1000
1234
+
1235
+ return (
1236
+ <div
1237
+ style={{
1238
+ display: 'inline-flex',
1239
+ alignItems: 'center',
1240
+ gap: '.25rem',
1241
+ color: age > staleTime ? theme.warning : undefined,
1242
+ }}
1243
+ >
1244
+ <div style={{}}>{formatTime(age)}</div>
1245
+ <div>/</div>
1246
+ <div>{formatTime(staleTime)}</div>
1247
+ <div>/</div>
1248
+ <div>{formatTime(gcTime)}</div>
1249
+ </div>
1250
+ )
1251
+ }
1252
+
1253
+ function formatTime(ms: number) {
1254
+ const units = ['s', 'min', 'h', 'd']
1255
+ const values = [ms / 1000, ms / 60000, ms / 3600000, ms / 86400000]
1256
+
1257
+ let chosenUnitIndex = 0
1258
+ for (let i = 1; i < values.length; i++) {
1259
+ if (values[i]! < 1) break
1260
+ chosenUnitIndex = i
1261
+ }
1262
+
1263
+ const formatter = new Intl.NumberFormat(navigator.language, {
1264
+ compactDisplay: 'short',
1265
+ notation: 'compact',
1266
+ maximumFractionDigits: 0,
1267
+ })
1268
+
1269
+ return formatter.format(values[chosenUnitIndex]!) + units[chosenUnitIndex]
1270
+ }