@tanstack/router-devtools 0.0.1-beta.99 → 1.0.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.
Files changed (38) hide show
  1. package/LICENSE +1 -1
  2. package/build/cjs/Explorer.js +10 -8
  3. package/build/cjs/Explorer.js.map +1 -1
  4. package/build/cjs/_virtual/_rollupPluginBabelHelpers.js +2 -4
  5. package/build/cjs/_virtual/_rollupPluginBabelHelpers.js.map +1 -1
  6. package/build/cjs/devtools.js +351 -106
  7. package/build/cjs/devtools.js.map +1 -1
  8. package/build/cjs/index.js +1 -3
  9. package/build/cjs/index.js.map +1 -1
  10. package/build/cjs/styledComponents.js +1 -4
  11. package/build/cjs/styledComponents.js.map +1 -1
  12. package/build/cjs/theme.js +10 -16
  13. package/build/cjs/theme.js.map +1 -1
  14. package/build/cjs/useLocalStorage.js +5 -9
  15. package/build/cjs/useLocalStorage.js.map +1 -1
  16. package/build/cjs/useMediaQuery.js +4 -8
  17. package/build/cjs/useMediaQuery.js.map +1 -1
  18. package/build/cjs/utils.js +36 -16
  19. package/build/cjs/utils.js.map +1 -1
  20. package/build/esm/index.js +342 -65
  21. package/build/esm/index.js.map +1 -1
  22. package/build/stats-html.html +3494 -2700
  23. package/build/stats-react.json +393 -147
  24. package/build/types/Explorer.d.ts +10 -4
  25. package/build/types/devtools.d.ts +1 -1
  26. package/build/types/index.d.ts +77 -1
  27. package/build/types/styledComponents.d.ts +3 -3
  28. package/build/types/theme.d.ts +13 -13
  29. package/build/types/utils.d.ts +3 -2
  30. package/build/umd/index.development.js +662 -145
  31. package/build/umd/index.development.js.map +1 -1
  32. package/build/umd/index.production.js +4 -14
  33. package/build/umd/index.production.js.map +1 -1
  34. package/package.json +2 -3
  35. package/src/Explorer.tsx +5 -0
  36. package/src/devtools.tsx +574 -287
  37. package/src/theme.tsx +6 -6
  38. package/src/utils.ts +14 -4
package/src/devtools.tsx CHANGED
@@ -1,14 +1,19 @@
1
1
  import React from 'react'
2
2
  import {
3
- last,
4
- routerContext,
5
3
  invariant,
6
4
  AnyRouter,
7
- useStore,
8
- } from '@tanstack/router'
5
+ Route,
6
+ AnyRoute,
7
+ AnyRootRoute,
8
+ trimPath,
9
+ useRouter,
10
+ useRouterState,
11
+ AnyRouteMatch,
12
+ } from '@tanstack/react-router'
9
13
 
10
14
  import useLocalStorage from './useLocalStorage'
11
15
  import {
16
+ getRouteStatusColor,
12
17
  getStatusColor,
13
18
  multiSortBy,
14
19
  useIsMounted,
@@ -336,16 +341,16 @@ export function TanStackRouterDevtools({
336
341
  right: '0',
337
342
  }
338
343
  : position === 'top-left'
339
- ? {
340
- left: '0',
341
- }
342
- : position === 'bottom-right'
343
- ? {
344
- right: '0',
345
- }
346
- : {
347
- left: '0',
348
- }),
344
+ ? {
345
+ left: '0',
346
+ }
347
+ : position === 'bottom-right'
348
+ ? {
349
+ right: '0',
350
+ }
351
+ : {
352
+ left: '0',
353
+ }),
349
354
  ...closeButtonStyle,
350
355
  }}
351
356
  >
@@ -380,19 +385,19 @@ export function TanStackRouterDevtools({
380
385
  right: '0',
381
386
  }
382
387
  : position === 'top-left'
383
- ? {
384
- top: '0',
385
- left: '0',
386
- }
387
- : position === 'bottom-right'
388
- ? {
389
- bottom: '0',
390
- right: '0',
391
- }
392
- : {
393
- bottom: '0',
394
- left: '0',
395
- }),
388
+ ? {
389
+ top: '0',
390
+ left: '0',
391
+ }
392
+ : position === 'bottom-right'
393
+ ? {
394
+ bottom: '0',
395
+ right: '0',
396
+ }
397
+ : {
398
+ bottom: '0',
399
+ left: '0',
400
+ }),
396
401
  ...toggleButtonStyle,
397
402
  }}
398
403
  >
@@ -403,6 +408,111 @@ export function TanStackRouterDevtools({
403
408
  )
404
409
  }
405
410
 
411
+ function RouteComp({
412
+ route,
413
+ isRoot,
414
+ activeId,
415
+ setActiveId,
416
+ }: {
417
+ route: AnyRootRoute | AnyRoute
418
+ isRoot?: boolean
419
+ activeId: string | undefined
420
+ setActiveId: (id: string) => void
421
+ }) {
422
+ const routerState = useRouterState()
423
+ const matches =
424
+ routerState.status === 'pending'
425
+ ? routerState.pendingMatches ?? []
426
+ : routerState.matches
427
+
428
+ const match = routerState.matches.find((d) => d.routeId === route.id)
429
+
430
+ return (
431
+ <div>
432
+ <div
433
+ role="button"
434
+ aria-label={`Open match details for ${route.id}`}
435
+ onClick={() => {
436
+ if (match) {
437
+ setActiveId(activeId === route.id ? '' : route.id)
438
+ }
439
+ }}
440
+ style={{
441
+ display: 'flex',
442
+ borderBottom: `solid 1px ${theme.grayAlt}`,
443
+ cursor: match ? 'pointer' : 'default',
444
+ alignItems: 'center',
445
+ background:
446
+ route.id === activeId ? 'rgba(255,255,255,.1)' : undefined,
447
+ padding: '.25rem .5rem',
448
+ gap: '.5rem',
449
+ }}
450
+ >
451
+ {isRoot ? null : (
452
+ <div
453
+ style={{
454
+ flex: '0 0 auto',
455
+ width: '.7rem',
456
+ height: '.7rem',
457
+ alignItems: 'center',
458
+ justifyContent: 'center',
459
+ fontWeight: 'bold',
460
+ borderRadius: '100%',
461
+ transition: 'all .2s ease-out',
462
+ background: getRouteStatusColor(matches, route, theme),
463
+ opacity: match ? 1 : 0.3,
464
+ }}
465
+ />
466
+ )}
467
+ <div
468
+ style={{
469
+ flex: '1 0 auto',
470
+ display: 'flex',
471
+ justifyContent: 'space-between',
472
+ alignItems: 'center',
473
+ padding: isRoot ? '0 .25rem' : 0,
474
+ opacity: match ? 1 : 0.7,
475
+ fontSize: '0.7rem',
476
+ }}
477
+ >
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>
490
+ </div>
491
+ {(route.children as Route[])?.length ? (
492
+ <div
493
+ style={{
494
+ marginLeft: isRoot ? 0 : '1rem',
495
+ borderLeft: isRoot ? '' : `solid 1px ${theme.grayAlt}`,
496
+ }}
497
+ >
498
+ {[...(route.children as Route[])]
499
+ .sort((a, b) => {
500
+ return a.rank - b.rank
501
+ })
502
+ .map((r) => (
503
+ <RouteComp
504
+ key={r.id}
505
+ route={r}
506
+ activeId={activeId}
507
+ setActiveId={setActiveId}
508
+ />
509
+ ))}
510
+ </div>
511
+ ) : null}
512
+ </div>
513
+ )
514
+ }
515
+
406
516
  export const TanStackRouterDevtoolsPanel = React.forwardRef<
407
517
  HTMLDivElement,
408
518
  DevtoolsPanelOptions
@@ -415,41 +525,43 @@ export const TanStackRouterDevtoolsPanel = React.forwardRef<
415
525
  ...panelProps
416
526
  } = props
417
527
 
418
- const routerContextValue = React.useContext(routerContext)
419
- const router = userRouter ?? routerContextValue?.router
528
+ const router = useRouter()
529
+ const routerState = useRouterState()
530
+
531
+ const matches = [
532
+ ...(routerState.pendingMatches ?? []),
533
+ ...routerState.matches,
534
+ ...routerState.cachedMatches,
535
+ ]
420
536
 
421
537
  invariant(
422
538
  router,
423
539
  'No router was found for the TanStack Router Devtools. Please place the devtools in the <RouterProvider> component tree or pass the router instance to the devtools manually.',
424
540
  )
425
541
 
426
- useStore(router.__store)
542
+ // useStore(router.__store)
427
543
 
428
- const [activeRouteId, setActiveRouteId] = useLocalStorage(
429
- 'tanstackRouterDevtoolsActiveRouteId',
430
- '',
544
+ const [showMatches, setShowMatches] = useLocalStorage(
545
+ 'tanstackRouterDevtoolsShowMatches',
546
+ true,
431
547
  )
432
548
 
433
- const [activeMatchId, setActiveMatchId] = useLocalStorage(
434
- 'tanstackRouterDevtoolsActiveMatchId',
549
+ const [activeId, setActiveId] = useLocalStorage(
550
+ 'tanstackRouterDevtoolsActiveRouteId',
435
551
  '',
436
552
  )
437
553
 
438
- React.useEffect(() => {
439
- setActiveMatchId('')
440
- }, [activeRouteId])
441
-
442
- const allMatches = React.useMemo(
443
- () => [
444
- ...Object.values(router.state.currentMatches),
445
- ...Object.values(router.state.pendingMatches ?? []),
446
- ],
447
- [router.state.currentMatches, router.state.pendingMatches],
554
+ const activeMatch = React.useMemo(
555
+ () => matches.find((d) => d.routeId === activeId || d.id === activeId),
556
+ [matches, activeId],
448
557
  )
449
558
 
450
- const activeMatch =
451
- allMatches?.find((d) => d.id === activeMatchId) ||
452
- allMatches?.find((d) => d.route.id === activeRouteId)
559
+ const hasSearch = Object.keys(routerState.location.search || {}).length
560
+
561
+ const explorerState = {
562
+ ...router,
563
+ state: router.state,
564
+ }
453
565
 
454
566
  return (
455
567
  <ThemeProvider theme={theme}>
@@ -562,7 +674,49 @@ export const TanStackRouterDevtoolsPanel = React.forwardRef<
562
674
  padding: '.5em',
563
675
  }}
564
676
  >
565
- <Explorer label="Router" value={router} defaultExpanded={{}} />
677
+ <Explorer
678
+ label="Router"
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
+ }}
716
+ filterSubEntries={(subEntries) => {
717
+ return subEntries.filter((d) => typeof d.value !== 'function')
718
+ }}
719
+ />
566
720
  </div>
567
721
  </div>
568
722
  </div>
@@ -579,220 +733,280 @@ export const TanStackRouterDevtoolsPanel = React.forwardRef<
579
733
  >
580
734
  <div
581
735
  style={{
582
- padding: '.5em',
583
- background: theme.backgroundAlt,
584
- position: 'sticky',
585
- top: 0,
586
- zIndex: 1,
736
+ flex: '1 1 auto',
737
+ overflowY: 'auto',
587
738
  }}
588
739
  >
589
- Active Matches
590
- </div>
591
- {router.state.currentMatches.map((match, i) => {
592
- return (
740
+ <div
741
+ style={{
742
+ padding: '.5em',
743
+ background: theme.backgroundAlt,
744
+ position: 'sticky',
745
+ top: 0,
746
+ zIndex: 1,
747
+ display: 'flex',
748
+ alignItems: 'center',
749
+ gap: '.5rem',
750
+ fontWeight: 'bold',
751
+ }}
752
+ >
753
+ Pathname{' '}
754
+ {routerState.location.maskedLocation ? (
755
+ <div
756
+ style={{
757
+ padding: '.1rem .5rem',
758
+ background: theme.warning,
759
+ color: 'black',
760
+ borderRadius: '.5rem',
761
+ }}
762
+ >
763
+ Masked
764
+ </div>
765
+ ) : null}
766
+ </div>
767
+ <div
768
+ style={{
769
+ padding: '.5rem',
770
+ display: 'flex',
771
+ gap: '.5rem',
772
+ alignItems: 'center',
773
+ }}
774
+ >
775
+ <code
776
+ style={{
777
+ opacity: 0.6,
778
+ }}
779
+ >
780
+ {routerState.location.pathname}
781
+ </code>
782
+ {routerState.location.maskedLocation ? (
783
+ <code
784
+ style={{
785
+ color: theme.warning,
786
+ fontWeight: 'bold',
787
+ }}
788
+ >
789
+ {routerState.location.maskedLocation.pathname}
790
+ </code>
791
+ ) : null}
792
+ </div>
793
+ <div
794
+ style={{
795
+ padding: '.5em',
796
+ background: theme.backgroundAlt,
797
+ position: 'sticky',
798
+ top: 0,
799
+ zIndex: 1,
800
+ display: 'flex',
801
+ alignItems: 'center',
802
+ justifyContent: 'space-between',
803
+ gap: '.5rem',
804
+ fontWeight: 'bold',
805
+ }}
806
+ >
593
807
  <div
594
- key={match.route.id || i}
595
- role="button"
596
- aria-label={`Open match details for ${match.route.id}`}
597
- onClick={() =>
598
- setActiveRouteId(
599
- activeRouteId === match.route.id ? '' : match.route.id,
600
- )
601
- }
602
808
  style={{
603
809
  display: 'flex',
604
- borderBottom: `solid 1px ${theme.grayAlt}`,
605
- cursor: 'pointer',
606
810
  alignItems: 'center',
607
- background:
608
- match === activeMatch ? 'rgba(255,255,255,.1)' : undefined,
811
+ gap: '.5rem',
609
812
  }}
610
813
  >
611
- <div
814
+ <button
815
+ type="button"
816
+ onClick={() => {
817
+ setShowMatches(false)
818
+ }}
819
+ disabled={!showMatches}
612
820
  style={{
613
- flex: '0 0 auto',
614
- width: '1.3rem',
615
- height: '1.3rem',
616
- marginLeft: '.25rem',
617
- background: getStatusColor(match, theme),
618
- alignItems: 'center',
619
- justifyContent: 'center',
620
- fontWeight: 'bold',
621
- borderRadius: '.25rem',
622
- transition: 'all .2s ease-out',
821
+ appearance: 'none',
822
+ opacity: showMatches ? 0.5 : 1,
823
+ border: 0,
824
+ background: 'transparent',
825
+ color: 'inherit',
826
+ cursor: 'pointer',
623
827
  }}
624
- />
625
-
626
- <Code
828
+ >
829
+ Routes
830
+ </button>
831
+ /
832
+ <button
833
+ type="button"
834
+ onClick={() => {
835
+ setShowMatches(true)
836
+ }}
837
+ disabled={showMatches}
627
838
  style={{
628
- padding: '.5em',
839
+ appearance: 'none',
840
+ opacity: !showMatches ? 0.5 : 1,
841
+ border: 0,
842
+ background: 'transparent',
843
+ color: 'inherit',
844
+ cursor: 'pointer',
629
845
  }}
630
846
  >
631
- {`${match.id}`}
632
- </Code>
847
+ Matches
848
+ </button>
633
849
  </div>
634
- )
635
- })}
636
- {router.state.pendingMatches?.length ? (
637
- <>
638
850
  <div
639
851
  style={{
640
- marginTop: '2rem',
641
- padding: '.5em',
642
- background: theme.backgroundAlt,
643
- position: 'sticky',
644
- top: 0,
645
- zIndex: 1,
852
+ opacity: 0.3,
853
+ fontSize: '0.7rem',
854
+ fontWeight: 'normal',
646
855
  }}
647
856
  >
648
- Pending Matches
857
+ age / staleTime / gcTime
649
858
  </div>
650
- {router.state.pendingMatches?.map((match, i) => {
651
- return (
652
- <div
653
- key={match.route.id || i}
654
- role="button"
655
- aria-label={`Open match details for ${match.route.id}`}
656
- onClick={() =>
657
- setActiveRouteId(
658
- activeRouteId === match.route.id ? '' : match.route.id,
659
- )
660
- }
661
- style={{
662
- display: 'flex',
663
- borderBottom: `solid 1px ${theme.grayAlt}`,
664
- cursor: 'pointer',
665
- background:
666
- match === activeMatch
667
- ? 'rgba(255,255,255,.1)'
668
- : undefined,
669
- }}
670
- >
859
+ </div>
860
+ {!showMatches ? (
861
+ <RouteComp
862
+ route={router.routeTree}
863
+ isRoot
864
+ activeId={activeId}
865
+ setActiveId={setActiveId}
866
+ />
867
+ ) : (
868
+ <div>
869
+ {(routerState.status === 'pending'
870
+ ? routerState.pendingMatches ?? []
871
+ : routerState.matches
872
+ ).map((match, i) => {
873
+ return (
671
874
  <div
875
+ key={match.id || i}
876
+ role="button"
877
+ aria-label={`Open match details for ${match.id}`}
878
+ onClick={() =>
879
+ setActiveId(activeId === match.id ? '' : match.id)
880
+ }
672
881
  style={{
673
- flex: '0 0 auto',
674
- width: '1.3rem',
675
- height: '1.3rem',
676
- marginLeft: '.25rem',
677
- background: getStatusColor(match, theme),
882
+ display: 'flex',
883
+ borderBottom: `solid 1px ${theme.grayAlt}`,
884
+ cursor: 'pointer',
678
885
  alignItems: 'center',
679
- justifyContent: 'center',
680
- fontWeight: 'bold',
681
- borderRadius: '.25rem',
682
- transition: 'all .2s ease-out',
683
- }}
684
- />
685
-
686
- <Code
687
- style={{
688
- padding: '.5em',
886
+ background:
887
+ match === activeMatch
888
+ ? 'rgba(255,255,255,.1)'
889
+ : undefined,
689
890
  }}
690
891
  >
691
- {`${match.id}`}
692
- </Code>
693
- </div>
694
- )
695
- })}
696
- </>
697
- ) : null}
698
- {/* {matchCacheValues.length ? (
699
- <>
892
+ <div
893
+ style={{
894
+ flex: '0 0 auto',
895
+ width: '1.3rem',
896
+ height: '1.3rem',
897
+ marginLeft: '.25rem',
898
+ background: getStatusColor(match, theme),
899
+ alignItems: 'center',
900
+ justifyContent: 'center',
901
+ fontWeight: 'bold',
902
+ borderRadius: '.25rem',
903
+ transition: 'all .2s ease-out',
904
+ }}
905
+ />
906
+
907
+ <Code
908
+ style={{
909
+ padding: '.5em',
910
+ fontSize: '0.7rem',
911
+ }}
912
+ >
913
+ {`${match.id}`}
914
+ </Code>
915
+ <AgeTicker match={match} />
916
+ </div>
917
+ )
918
+ })}
919
+ </div>
920
+ )}
921
+ </div>
922
+ {routerState.cachedMatches?.length ? (
923
+ <div
924
+ style={{
925
+ flex: '1 1 auto',
926
+ overflowY: 'auto',
927
+ maxHeight: '50%',
928
+ }}
929
+ >
700
930
  <div
701
931
  style={{
702
- marginTop: '2rem',
703
932
  padding: '.5em',
704
933
  background: theme.backgroundAlt,
705
934
  position: 'sticky',
706
935
  top: 0,
707
- bottom: 0,
708
936
  zIndex: 1,
709
937
  display: 'flex',
710
938
  alignItems: 'center',
711
939
  justifyContent: 'space-between',
940
+ gap: '.5rem',
941
+ fontWeight: 'bold',
712
942
  }}
713
943
  >
714
- <div>Match Cache</div>
715
- <Button
716
- onClick={() => {
717
- router.store.setState((s) => (s.matchCache = {}))
944
+ <div>Cached Matches</div>
945
+ <div
946
+ style={{
947
+ opacity: 0.3,
948
+ fontSize: '0.7rem',
949
+ fontWeight: 'normal',
718
950
  }}
719
951
  >
720
- Clear
721
- </Button>
952
+ age / staleTime / gcTime
953
+ </div>
722
954
  </div>
723
- {matchCacheValues.map((d, i) => {
724
- const { match, gc } = d
725
-
726
- return (
727
- <div
728
- key={match.id || i}
729
- role="button"
730
- aria-label={`Open match details for ${match.id}`}
731
- onClick={() =>
732
- setActiveMatchId(
733
- activeMatchId === match.id ? '' : match.id,
734
- )
735
- }
736
- style={{
737
- display: 'flex',
738
- borderBottom: `solid 1px ${theme.grayAlt}`,
739
- cursor: 'pointer',
740
- background:
741
- match === activeMatch
742
- ? 'rgba(255,255,255,.1)'
743
- : undefined,
744
- }}
745
- >
955
+ <div>
956
+ {routerState.cachedMatches.map((match) => {
957
+ return (
746
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
+ }
747
965
  style={{
748
966
  display: 'flex',
749
- flexDirection: 'column',
750
- padding: '.5rem',
751
- gap: '.3rem',
967
+ borderBottom: `solid 1px ${theme.grayAlt}`,
968
+ cursor: 'pointer',
969
+ alignItems: 'center',
970
+ background:
971
+ match === activeMatch
972
+ ? 'rgba(255,255,255,.1)'
973
+ : undefined,
974
+ fontSize: '0.7rem',
752
975
  }}
753
976
  >
754
977
  <div
755
978
  style={{
756
- display: 'flex',
979
+ flex: '0 0 auto',
980
+ width: '.75rem',
981
+ height: '.75rem',
982
+ marginLeft: '.25rem',
983
+ background: getStatusColor(match, theme),
757
984
  alignItems: 'center',
758
- gap: '.5rem',
985
+ justifyContent: 'center',
986
+ fontWeight: 'bold',
987
+ borderRadius: '100%',
988
+ transition: 'all 1s ease-out',
759
989
  }}
760
- >
761
- <div
762
- style={{
763
- flex: '0 0 auto',
764
- width: '1.3rem',
765
- height: '1.3rem',
766
- background: getStatusColor(match, theme),
767
- alignItems: 'center',
768
- justifyContent: 'center',
769
- fontWeight: 'bold',
770
- borderRadius: '.25rem',
771
- transition: 'all .2s ease-out',
772
- }}
773
- />
774
- <Code>{`${match.id}`}</Code>
775
- </div>
776
- <span
990
+ />
991
+
992
+ <Code
777
993
  style={{
778
- fontSize: '.7rem',
779
- opacity: '.5',
780
- lineHeight: 1,
994
+ padding: '.5em',
781
995
  }}
782
996
  >
783
- Expires{' '}
784
- {formatDistanceStrict(new Date(gc), new Date(), {
785
- addSuffix: true,
786
- })}
787
- </span>
997
+ {`${match.id}`}
998
+ </Code>
999
+
1000
+ <div style={{ marginLeft: 'auto' }}>
1001
+ <AgeTicker match={match} />
1002
+ </div>
788
1003
  </div>
789
- </div>
790
- )
791
- })}
792
- </>
793
- ) : null} */}
1004
+ )
1005
+ })}
1006
+ </div>
1007
+ </div>
1008
+ ) : null}
794
1009
  </div>
795
-
796
1010
  {activeMatch ? (
797
1011
  <ActivePanel>
798
1012
  <div
@@ -808,7 +1022,11 @@ export const TanStackRouterDevtoolsPanel = React.forwardRef<
808
1022
  Match Details
809
1023
  </div>
810
1024
  <div>
811
- <table>
1025
+ <table
1026
+ style={{
1027
+ fontSize: '0.8rem',
1028
+ }}
1029
+ >
812
1030
  <tbody>
813
1031
  <tr>
814
1032
  <td style={{ opacity: '.5' }}>ID</td>
@@ -824,7 +1042,18 @@ export const TanStackRouterDevtoolsPanel = React.forwardRef<
824
1042
  </tr>
825
1043
  <tr>
826
1044
  <td style={{ opacity: '.5' }}>Status</td>
827
- <td>{activeMatch.state.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>
828
1057
  </tr>
829
1058
  {/* <tr>
830
1059
  <td style={{ opacity: '.5' }}>Invalid</td>
@@ -833,9 +1062,9 @@ export const TanStackRouterDevtoolsPanel = React.forwardRef<
833
1062
  <tr>
834
1063
  <td style={{ opacity: '.5' }}>Last Updated</td>
835
1064
  <td>
836
- {activeMatch.state.updatedAt
1065
+ {activeMatch.updatedAt
837
1066
  ? new Date(
838
- activeMatch.state.updatedAt as number,
1067
+ activeMatch.updatedAt as number,
839
1068
  ).toLocaleTimeString()
840
1069
  : 'N/A'}
841
1070
  </td>
@@ -843,7 +1072,7 @@ export const TanStackRouterDevtoolsPanel = React.forwardRef<
843
1072
  </tbody>
844
1073
  </table>
845
1074
  </div>
846
- <div
1075
+ {/* <div
847
1076
  style={{
848
1077
  background: theme.backgroundAlt,
849
1078
  padding: '.5em',
@@ -862,14 +1091,41 @@ export const TanStackRouterDevtoolsPanel = React.forwardRef<
862
1091
  >
863
1092
  <Button
864
1093
  type="button"
865
- onClick={() => activeMatch.load()}
1094
+ onClick={() => activeMatch.__store.setState(d => ({...d, status: 'pending'}))}
866
1095
  style={{
867
1096
  background: theme.gray,
868
1097
  }}
869
1098
  >
870
1099
  Reload
871
1100
  </Button>
872
- </div>
1101
+ </div> */}
1102
+ {activeMatch.loaderData ? (
1103
+ <>
1104
+ <div
1105
+ style={{
1106
+ background: theme.backgroundAlt,
1107
+ padding: '.5em',
1108
+ position: 'sticky',
1109
+ top: 0,
1110
+ bottom: 0,
1111
+ zIndex: 1,
1112
+ }}
1113
+ >
1114
+ Loader Data
1115
+ </div>
1116
+ <div
1117
+ style={{
1118
+ padding: '.5em',
1119
+ }}
1120
+ >
1121
+ <Explorer
1122
+ label="loaderData"
1123
+ value={activeMatch.loaderData}
1124
+ defaultExpanded={{}}
1125
+ />
1126
+ </div>
1127
+ </>
1128
+ ) : null}
873
1129
  <div
874
1130
  style={{
875
1131
  background: theme.backgroundAlt,
@@ -895,89 +1151,120 @@ export const TanStackRouterDevtoolsPanel = React.forwardRef<
895
1151
  </div>
896
1152
  </ActivePanel>
897
1153
  ) : null}
898
- <div
899
- style={{
900
- flex: '1 1 500px',
901
- minHeight: '40%',
902
- maxHeight: '100%',
903
- overflow: 'auto',
904
- borderRight: `1px solid ${theme.grayAlt}`,
905
- display: 'flex',
906
- flexDirection: 'column',
907
- }}
908
- >
909
- {/* <div
910
- style={{
911
- padding: '.5em',
912
- background: theme.backgroundAlt,
913
- position: 'sticky',
914
- top: 0,
915
- bottom: 0,
916
- zIndex: 1,
917
- }}
918
- >
919
- All Loader Data
920
- </div>
1154
+ {hasSearch ? (
921
1155
  <div
922
1156
  style={{
923
- padding: '.5em',
924
- }}
925
- >
926
- {Object.keys(
927
- last(router.state.currentMatches)?.state.loaderData ||
928
- {},
929
- ).length ? (
930
- <Explorer
931
- value={
932
- last(router.state.currentMatches)?.state
933
- .loaderData || {}
934
- }
935
- defaultExpanded={Object.keys(
936
- (last(router.state.currentMatches)?.state
937
- .loaderData as {}) || {},
938
- ).reduce((obj: any, next) => {
939
- obj[next] = {}
940
- return obj
941
- }, {})}
942
- />
943
- ) : (
944
- <em style={{ opacity: 0.5 }}>{'{ }'}</em>
945
- )}
946
- </div> */}
947
- <div
948
- style={{
949
- padding: '.5em',
950
- background: theme.backgroundAlt,
951
- position: 'sticky',
952
- top: 0,
953
- bottom: 0,
954
- zIndex: 1,
955
- }}
956
- >
957
- Search Params
958
- </div>
959
- <div
960
- style={{
961
- padding: '.5em',
1157
+ flex: '1 1 500px',
1158
+ minHeight: '40%',
1159
+ maxHeight: '100%',
1160
+ overflow: 'auto',
1161
+ borderRight: `1px solid ${theme.grayAlt}`,
1162
+ display: 'flex',
1163
+ flexDirection: 'column',
962
1164
  }}
963
1165
  >
964
- {Object.keys(last(router.state.currentMatches)?.state.search || {})
965
- .length ? (
1166
+ <div
1167
+ style={{
1168
+ padding: '.5em',
1169
+ background: theme.backgroundAlt,
1170
+ position: 'sticky',
1171
+ top: 0,
1172
+ bottom: 0,
1173
+ zIndex: 1,
1174
+ fontWeight: 'bold',
1175
+ }}
1176
+ >
1177
+ Search Params
1178
+ </div>
1179
+ <div
1180
+ style={{
1181
+ padding: '.5em',
1182
+ }}
1183
+ >
966
1184
  <Explorer
967
- value={last(router.state.currentMatches)?.state.search || {}}
1185
+ value={routerState.location.search || {}}
968
1186
  defaultExpanded={Object.keys(
969
- (last(router.state.currentMatches)?.state.search as {}) || {},
1187
+ (routerState.location.search as {}) || {},
970
1188
  ).reduce((obj: any, next) => {
971
1189
  obj[next] = {}
972
1190
  return obj
973
1191
  }, {})}
974
1192
  />
975
- ) : (
976
- <em style={{ opacity: 0.5 }}>{'{ }'}</em>
977
- )}
1193
+ </div>
978
1194
  </div>
979
- </div>
1195
+ ) : null}
980
1196
  </Panel>
981
1197
  </ThemeProvider>
982
1198
  )
983
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
+ }