@tanstack/react-router-devtools 0.0.1-alpha.5 → 0.0.1-alpha.7

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/react-router-devtools",
3
3
  "author": "Tanner Linsley",
4
- "version": "0.0.1-alpha.5",
4
+ "version": "0.0.1-alpha.7",
5
5
  "license": "MIT",
6
6
  "repository": "tanstack/router",
7
7
  "homepage": "https://react-router.tanstack.com/",
@@ -35,7 +35,7 @@
35
35
  "src"
36
36
  ],
37
37
  "peerDependencies": {
38
- "@tanstack/react-router": "0.0.1-alpha.5",
38
+ "@tanstack/react-router": "0.0.1-alpha.7",
39
39
  "react": ">=16",
40
40
  "react-dom": ">=16"
41
41
  },
package/src/devtools.tsx CHANGED
@@ -1,9 +1,14 @@
1
1
  import React from 'react'
2
- import { Router, useRouter, last } from '@tanstack/react-router'
2
+ import { Router, last } from '@tanstack/react-router'
3
3
  import { formatDistanceStrict } from 'date-fns'
4
4
 
5
5
  import useLocalStorage from './useLocalStorage'
6
- import { getStatusColor, useIsMounted, useSafeState } from './utils'
6
+ import {
7
+ getStatusColor,
8
+ multiSortBy,
9
+ useIsMounted,
10
+ useSafeState,
11
+ } from './utils'
7
12
  import { Panel, Button, Code, ActivePanel } from './styledComponents'
8
13
  import { ThemeProvider, defaultTheme as theme } from './theme'
9
14
  // import { getQueryStatusLabel, getQueryStatusColor } from './utils'
@@ -52,7 +57,7 @@ interface DevtoolsOptions {
52
57
  /**
53
58
  * A boolean variable indicating if the "lite" version of the library is being used
54
59
  */
55
- useRouter?: () => unknown
60
+ router: Router<any, any>
56
61
  }
57
62
 
58
63
  interface DevtoolsPanelOptions {
@@ -79,7 +84,7 @@ interface DevtoolsPanelOptions {
79
84
  /**
80
85
  * A boolean variable indicating if the "lite" version of the library is being used
81
86
  */
82
- useRouter: () => unknown
87
+ router: Router<any, any>
83
88
  }
84
89
 
85
90
  const isServer = typeof window === 'undefined'
@@ -91,7 +96,7 @@ export function TanStackRouterDevtools({
91
96
  toggleButtonProps = {},
92
97
  position = 'bottom-left',
93
98
  containerElement: Container = 'footer',
94
- useRouter: useRouterImpl = useRouter,
99
+ router,
95
100
  }: DevtoolsOptions): React.ReactElement | null {
96
101
  const rootRef = React.useRef<HTMLDivElement>(null)
97
102
  const panelRef = React.useRef<HTMLDivElement>(null)
@@ -230,7 +235,7 @@ export function TanStackRouterDevtools({
230
235
  <TanStackRouterDevtoolsPanel
231
236
  ref={panelRef as any}
232
237
  {...otherPanelProps}
233
- useRouter={useRouterImpl}
238
+ router={router}
234
239
  style={{
235
240
  position: 'fixed',
236
241
  bottom: '0',
@@ -360,42 +365,15 @@ export const TanStackRouterDevtoolsPanel = React.forwardRef<
360
365
  isOpen = true,
361
366
  setIsOpen,
362
367
  handleDragStart,
363
- useRouter,
368
+ router,
364
369
  ...panelProps
365
370
  } = props
366
371
 
367
- const router = useRouter() as Router
368
- const routerExplorerValue = React.useMemo(() => {
369
- const {
370
- listeners,
371
- buildLocation,
372
- mount,
373
- update,
374
- buildNext,
375
- navigate,
376
- cancelMatches,
377
- loadLocation,
378
- cleanPreloadCache,
379
- loadRoute,
380
- matchRoutes,
381
- loadMatches,
382
- invalidateRoute,
383
- resolvePath,
384
- matchRoute,
385
- buildLink,
386
- __experimental__createSnapshot,
387
- destroy,
388
- ...rest
389
- } = router
390
-
391
- return rest
392
- }, [router.state])
393
-
394
372
  const rerender = React.useReducer(() => ({}), {})[1]
395
373
 
396
374
  React.useEffect(() => {
397
375
  let interval = setInterval(() => {
398
- router.cleanPreloadCache()
376
+ router.cleanMatchCache()
399
377
  // router.notify()
400
378
  rerender()
401
379
  }, 250)
@@ -410,30 +388,29 @@ export const TanStackRouterDevtoolsPanel = React.forwardRef<
410
388
  '',
411
389
  )
412
390
 
413
- const activeMatch = router.state.matches?.find(
414
- (d) => d.routeId === activeRouteId,
391
+ const [activeMatchId, setActiveMatchId] = useLocalStorage(
392
+ 'tanstackRouterDevtoolsActiveMatchId',
393
+ '',
415
394
  )
416
395
 
417
- const activeMatchExplorerValue = React.useMemo(() => {
418
- if (!activeMatch) {
419
- return {}
420
- }
421
-
422
- const {
423
- cancel,
424
- load,
425
- router,
426
- Link,
427
- MatchRoute,
428
- buildLink,
429
- linkProps,
430
- matchRoute,
431
- navigate,
432
- ...rest
433
- } = activeMatch
434
-
435
- return rest
436
- }, [activeMatch])
396
+ React.useEffect(() => {
397
+ setActiveMatchId('')
398
+ }, [activeRouteId])
399
+
400
+ const activeMatch =
401
+ Object.values(router.matchCache)?.find(
402
+ (d) => d.match.matchId === activeMatchId,
403
+ )?.match ?? router.state.matches?.find((d) => d.routeId === activeRouteId)
404
+
405
+ const matchCacheValues = multiSortBy(
406
+ Object.keys(router.matchCache)
407
+ .filter((key) => {
408
+ const cacheEntry = router.matchCache[key]!
409
+ return cacheEntry.gc > Date.now()
410
+ })
411
+ .map((key) => router.matchCache[key]!),
412
+ [(d) => (d.match.isFetching ? -1 : 1), (d) => -d.match.updatedAt!],
413
+ )
437
414
 
438
415
  return (
439
416
  <ThemeProvider theme={theme}>
@@ -552,11 +529,7 @@ export const TanStackRouterDevtoolsPanel = React.forwardRef<
552
529
  padding: '.5em',
553
530
  }}
554
531
  >
555
- <Explorer
556
- label="Router"
557
- value={routerExplorerValue}
558
- defaultExpanded={{}}
559
- />
532
+ <Explorer label="Router" value={router} defaultExpanded={{}} />
560
533
  </div>
561
534
  </div>
562
535
  </div>
@@ -580,7 +553,7 @@ export const TanStackRouterDevtoolsPanel = React.forwardRef<
580
553
  zIndex: 1,
581
554
  }}
582
555
  >
583
- Current Matches
556
+ Active Matches
584
557
  </div>
585
558
  {router.state.matches.map((match, i) => {
586
559
  return (
@@ -627,153 +600,162 @@ export const TanStackRouterDevtoolsPanel = React.forwardRef<
627
600
  </div>
628
601
  )
629
602
  })}
630
- <div
631
- style={{
632
- marginTop: '2rem',
633
- padding: '.5em',
634
- background: theme.backgroundAlt,
635
- position: 'sticky',
636
- top: 0,
637
- zIndex: 1,
638
- }}
639
- >
640
- Pending Matches
641
- </div>
642
- {router.state.pending?.matches.map((match, i) => {
643
- return (
603
+ {router.state.pending?.matches.length ? (
604
+ <>
644
605
  <div
645
- key={match.routeId || i}
646
- role="button"
647
- aria-label={`Open match details for ${match.routeId}`}
648
- onClick={() =>
649
- setActiveRouteId(
650
- activeRouteId === match.routeId ? '' : match.routeId,
651
- )
652
- }
653
606
  style={{
654
- display: 'flex',
655
- borderBottom: `solid 1px ${theme.grayAlt}`,
656
- cursor: 'pointer',
657
- background:
658
- match === activeMatch ? 'rgba(255,255,255,.1)' : undefined,
607
+ marginTop: '2rem',
608
+ padding: '.5em',
609
+ background: theme.backgroundAlt,
610
+ position: 'sticky',
611
+ top: 0,
612
+ zIndex: 1,
659
613
  }}
660
614
  >
661
- <div
662
- style={{
663
- flex: '0 0 auto',
664
- width: '1.3rem',
665
- height: '1.3rem',
666
- marginLeft: '.25rem',
667
- background: getStatusColor(match, theme),
668
- alignItems: 'center',
669
- justifyContent: 'center',
670
- fontWeight: 'bold',
671
- borderRadius: '.25rem',
672
- transition: 'all .2s ease-out',
673
- }}
674
- />
615
+ Pending Matches
616
+ </div>
617
+ {router.state.pending?.matches.map((match, i) => {
618
+ return (
619
+ <div
620
+ key={match.routeId || i}
621
+ role="button"
622
+ aria-label={`Open match details for ${match.routeId}`}
623
+ onClick={() =>
624
+ setActiveRouteId(
625
+ activeRouteId === match.routeId ? '' : match.routeId,
626
+ )
627
+ }
628
+ style={{
629
+ display: 'flex',
630
+ borderBottom: `solid 1px ${theme.grayAlt}`,
631
+ cursor: 'pointer',
632
+ background:
633
+ match === activeMatch
634
+ ? 'rgba(255,255,255,.1)'
635
+ : undefined,
636
+ }}
637
+ >
638
+ <div
639
+ style={{
640
+ flex: '0 0 auto',
641
+ width: '1.3rem',
642
+ height: '1.3rem',
643
+ marginLeft: '.25rem',
644
+ background: getStatusColor(match, theme),
645
+ alignItems: 'center',
646
+ justifyContent: 'center',
647
+ fontWeight: 'bold',
648
+ borderRadius: '.25rem',
649
+ transition: 'all .2s ease-out',
650
+ }}
651
+ />
675
652
 
676
- <Code
677
- style={{
678
- padding: '.5em',
653
+ <Code
654
+ style={{
655
+ padding: '.5em',
656
+ }}
657
+ >
658
+ {`${match.matchId}`}
659
+ </Code>
660
+ </div>
661
+ )
662
+ })}
663
+ </>
664
+ ) : null}
665
+ {matchCacheValues.length ? (
666
+ <>
667
+ <div
668
+ style={{
669
+ marginTop: '2rem',
670
+ padding: '.5em',
671
+ background: theme.backgroundAlt,
672
+ position: 'sticky',
673
+ top: 0,
674
+ zIndex: 1,
675
+ display: 'flex',
676
+ alignItems: 'center',
677
+ justifyContent: 'space-between',
678
+ }}
679
+ >
680
+ <div>Match Cache</div>
681
+ <Button
682
+ onClick={() => {
683
+ router.matchCache = {}
684
+ router.notify()
679
685
  }}
680
686
  >
681
- {`${match.matchId}`}
682
- </Code>
687
+ Clear
688
+ </Button>
683
689
  </div>
684
- )
685
- })}
686
- <div
687
- style={{
688
- marginTop: '2rem',
689
- padding: '.5em',
690
- background: theme.backgroundAlt,
691
- position: 'sticky',
692
- top: 0,
693
- zIndex: 1,
694
- }}
695
- >
696
- Preloading Matches
697
- </div>
698
- {Object.keys(router.preloadCache)
699
- .filter((key) => {
700
- const cacheEntry = router.preloadCache[key]!
701
- return (
702
- (cacheEntry.match.updatedAt ?? Date.now()) + cacheEntry.maxAge >
703
- Date.now()
704
- )
705
- })
706
- .map((key, i) => {
707
- const { match, maxAge } = router.preloadCache[key]!
708
-
709
- return (
710
- <div
711
- key={match.matchId || i}
712
- role="button"
713
- aria-label={`Open match details for ${match.matchId}`}
714
- onClick={() =>
715
- setActiveRouteId(
716
- activeRouteId === match.routeId ? '' : match.routeId,
717
- )
718
- }
719
- style={{
720
- display: 'flex',
721
- borderBottom: `solid 1px ${theme.grayAlt}`,
722
- cursor: 'pointer',
723
- background:
724
- match === activeMatch
725
- ? 'rgba(255,255,255,.1)'
726
- : undefined,
727
- }}
728
- >
690
+ {matchCacheValues.map((d, i) => {
691
+ const { match, gc } = d
692
+
693
+ return (
729
694
  <div
695
+ key={match.matchId || i}
696
+ role="button"
697
+ aria-label={`Open match details for ${match.matchId}`}
698
+ onClick={() =>
699
+ setActiveMatchId(
700
+ activeMatchId === match.matchId ? '' : match.matchId,
701
+ )
702
+ }
730
703
  style={{
731
704
  display: 'flex',
732
- flexDirection: 'column',
733
- padding: '.5rem',
734
- gap: '.3rem',
705
+ borderBottom: `solid 1px ${theme.grayAlt}`,
706
+ cursor: 'pointer',
707
+ background:
708
+ match === activeMatch
709
+ ? 'rgba(255,255,255,.1)'
710
+ : undefined,
735
711
  }}
736
712
  >
737
713
  <div
738
714
  style={{
739
715
  display: 'flex',
740
- alignItems: 'center',
741
- gap: '.5rem',
716
+ flexDirection: 'column',
717
+ padding: '.5rem',
718
+ gap: '.3rem',
742
719
  }}
743
720
  >
744
721
  <div
745
722
  style={{
746
- flex: '0 0 auto',
747
- width: '1.3rem',
748
- height: '1.3rem',
749
- background: getStatusColor(match, theme),
723
+ display: 'flex',
750
724
  alignItems: 'center',
751
- justifyContent: 'center',
752
- fontWeight: 'bold',
753
- borderRadius: '.25rem',
754
- transition: 'all .2s ease-out',
725
+ gap: '.5rem',
755
726
  }}
756
- />
757
- <Code>{`${match.matchId}`}</Code>
758
- </div>
759
- <span
760
- style={{
761
- opacity: '.5',
762
- }}
763
- >
764
- Expires{' '}
765
- {formatDistanceStrict(
766
- new Date(),
767
- new Date((match.updatedAt ?? Date.now()) + maxAge),
768
- {
727
+ >
728
+ <div
729
+ style={{
730
+ flex: '0 0 auto',
731
+ width: '1.3rem',
732
+ height: '1.3rem',
733
+ background: getStatusColor(match, theme),
734
+ alignItems: 'center',
735
+ justifyContent: 'center',
736
+ fontWeight: 'bold',
737
+ borderRadius: '.25rem',
738
+ transition: 'all .2s ease-out',
739
+ }}
740
+ />
741
+ <Code>{`${match.matchId}`}</Code>
742
+ </div>
743
+ <span
744
+ style={{
745
+ opacity: '.5',
746
+ }}
747
+ >
748
+ Expires{' '}
749
+ {formatDistanceStrict(new Date(), new Date(gc), {
769
750
  addSuffix: true,
770
- },
771
- )}
772
- </span>
751
+ })}
752
+ </span>
753
+ </div>
773
754
  </div>
774
- </div>
775
- )
776
- })}
755
+ )
756
+ })}
757
+ </>
758
+ ) : null}
777
759
  </div>
778
760
 
779
761
  {activeMatch ? (
@@ -848,7 +830,7 @@ export const TanStackRouterDevtoolsPanel = React.forwardRef<
848
830
  <Button
849
831
  type="button"
850
832
  onClick={() => {
851
- router.invalidateRoute(activeMatch as any)
833
+ activeMatch.invalidate()
852
834
  router.notify()
853
835
  }}
854
836
  style={{
@@ -860,7 +842,7 @@ export const TanStackRouterDevtoolsPanel = React.forwardRef<
860
842
  </Button>{' '}
861
843
  <Button
862
844
  type="button"
863
- onClick={() => router.reload()}
845
+ onClick={() => activeMatch.load()}
864
846
  style={{
865
847
  background: theme.gray,
866
848
  }}
@@ -886,7 +868,7 @@ export const TanStackRouterDevtoolsPanel = React.forwardRef<
886
868
  >
887
869
  <Explorer
888
870
  label="Match"
889
- value={activeMatchExplorerValue}
871
+ value={activeMatch}
890
872
  defaultExpanded={{}}
891
873
  />
892
874
  </div>
package/src/utils.ts CHANGED
@@ -149,3 +149,33 @@ function scheduleMicrotask(callback: () => void) {
149
149
  }),
150
150
  )
151
151
  }
152
+
153
+ export function multiSortBy<T>(
154
+ arr: T[],
155
+ accessors: ((item: T) => any)[] = [(d) => d],
156
+ ): T[] {
157
+ return arr
158
+ .map((d, i) => [d, i] as const)
159
+ .sort(([a, ai], [b, bi]) => {
160
+ for (const accessor of accessors) {
161
+ const ao = accessor(a)
162
+ const bo = accessor(b)
163
+
164
+ if (typeof ao === 'undefined') {
165
+ if (typeof bo === 'undefined') {
166
+ continue
167
+ }
168
+ return 1
169
+ }
170
+
171
+ if (ao === bo) {
172
+ continue
173
+ }
174
+
175
+ return ao > bo ? 1 : -1
176
+ }
177
+
178
+ return ai - bi
179
+ })
180
+ .map(([d]) => d)
181
+ }