@tanstack/router-devtools 1.35.6 → 1.36.1

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/src/devtools.tsx CHANGED
@@ -7,7 +7,7 @@ import {
7
7
  useRouterState,
8
8
  } from '@tanstack/react-router'
9
9
 
10
- import { css } from 'goober'
10
+ import * as goober from 'goober'
11
11
  import { clsx as cx } from 'clsx'
12
12
  import useLocalStorage from './useLocalStorage'
13
13
  import {
@@ -20,6 +20,11 @@ import {
20
20
  import Explorer from './Explorer'
21
21
  import { tokens } from './tokens'
22
22
  import { TanStackLogo } from './logo'
23
+ import {
24
+ DevtoolsOnCloseContext,
25
+ ShadowDomTargetContext,
26
+ useDevtoolsOnClose,
27
+ } from './context'
23
28
  import type {
24
29
  AnyRootRoute,
25
30
  AnyRoute,
@@ -69,6 +74,10 @@ interface DevtoolsOptions {
69
74
  * A boolean variable indicating if the "lite" version of the library is being used
70
75
  */
71
76
  router?: AnyRouter
77
+ /**
78
+ * Use this to attach the devtool's styles to specific element in the DOM.
79
+ */
80
+ shadowDOMTarget?: ShadowRoot
72
81
  }
73
82
 
74
83
  interface DevtoolsPanelOptions {
@@ -96,38 +105,38 @@ interface DevtoolsPanelOptions {
96
105
  * A boolean variable indicating if the "lite" version of the library is being used
97
106
  */
98
107
  router?: AnyRouter
108
+ /**
109
+ * Use this to attach the devtool's styles to specific element in the DOM.
110
+ */
111
+ shadowDOMTarget?: ShadowRoot
99
112
  }
100
113
 
101
114
  const isServer = typeof window === 'undefined'
102
115
 
103
116
  function Logo(props: React.HTMLAttributes<HTMLButtonElement>) {
104
117
  const { className, ...rest } = props
118
+ const styles = useStyles()
105
119
  return (
106
- <button {...rest} className={cx(getStyles().logo, className)}>
107
- <div className={getStyles().tanstackLogo}>TANSTACK</div>
108
- <div className={getStyles().routerLogo}>React Router v1</div>
120
+ <button {...rest} className={cx(styles.logo, className)}>
121
+ <div className={styles.tanstackLogo}>TANSTACK</div>
122
+ <div className={styles.routerLogo}>React Router v1</div>
109
123
  </button>
110
124
  )
111
125
  }
112
126
 
113
- const DevtoolsOnCloseContext = React.createContext<
114
- | {
115
- onCloseClick: (e: React.MouseEvent<HTMLButtonElement>) => void
116
- }
117
- | undefined
118
- >(undefined)
119
-
120
- const useDevtoolsOnClose = () => {
121
- const context = React.useContext(DevtoolsOnCloseContext)
122
- if (!context) {
123
- throw new Error(
124
- 'useDevtoolsOnClose must be used within a TanStackRouterDevtools component',
125
- )
126
- }
127
- return context
127
+ export function TanStackRouterDevtools(
128
+ props: DevtoolsOptions,
129
+ ): React.ReactElement | null {
130
+ const { shadowDOMTarget } = props
131
+
132
+ return (
133
+ <ShadowDomTargetContext.Provider value={shadowDOMTarget}>
134
+ <FloatingTanStackRouterDevtools {...props} />
135
+ </ShadowDomTargetContext.Provider>
136
+ )
128
137
  }
129
138
 
130
- export function TanStackRouterDevtools({
139
+ function FloatingTanStackRouterDevtools({
131
140
  initialIsOpen,
132
141
  panelProps = {},
133
142
  closeButtonProps = {},
@@ -135,6 +144,7 @@ export function TanStackRouterDevtools({
135
144
  position = 'bottom-left',
136
145
  containerElement: Container = 'footer',
137
146
  router,
147
+ shadowDOMTarget,
138
148
  }: DevtoolsOptions): React.ReactElement | null {
139
149
  const [rootEl, setRootEl] = React.useState<HTMLDivElement>()
140
150
  const panelRef = React.useRef<HTMLDivElement>(null)
@@ -149,6 +159,7 @@ export function TanStackRouterDevtools({
149
159
  const [isResolvedOpen, setIsResolvedOpen] = useSafeState(false)
150
160
  const [isResizing, setIsResizing] = useSafeState(false)
151
161
  const isMounted = useIsMounted()
162
+ const styles = useStyles()
152
163
 
153
164
  const handleDragStart = (
154
165
  panelElement: HTMLDivElement | null,
@@ -258,10 +269,10 @@ export function TanStackRouterDevtools({
258
269
  {...otherPanelProps}
259
270
  router={router}
260
271
  className={cx(
261
- getStyles().devtoolsPanelContainer,
262
- getStyles().devtoolsPanelContainerVisibility(!!isOpen),
263
- getStyles().devtoolsPanelContainerResizing(isResizing),
264
- getStyles().devtoolsPanelContainerAnimation(
272
+ styles.devtoolsPanelContainer,
273
+ styles.devtoolsPanelContainerVisibility(!!isOpen),
274
+ styles.devtoolsPanelContainerResizing(isResizing),
275
+ styles.devtoolsPanelContainerAnimation(
265
276
  isResolvedOpen,
266
277
  resolvedHeight + 16,
267
278
  ),
@@ -273,6 +284,7 @@ export function TanStackRouterDevtools({
273
284
  isOpen={isResolvedOpen}
274
285
  setIsOpen={setIsOpen}
275
286
  handleDragStart={(e) => handleDragStart(panelRef.current, e)}
287
+ shadowDOMTarget={shadowDOMTarget}
276
288
  />
277
289
  </DevtoolsOnCloseContext.Provider>
278
290
 
@@ -285,22 +297,22 @@ export function TanStackRouterDevtools({
285
297
  onToggleClick && onToggleClick(e)
286
298
  }}
287
299
  className={cx(
288
- getStyles().mainCloseBtn,
289
- getStyles().mainCloseBtnPosition(position),
290
- getStyles().mainCloseBtnAnimation(!isButtonClosed),
300
+ styles.mainCloseBtn,
301
+ styles.mainCloseBtnPosition(position),
302
+ styles.mainCloseBtnAnimation(!isButtonClosed),
291
303
  toggleButtonClassName,
292
304
  )}
293
305
  >
294
- <div className={getStyles().mainCloseBtnIconContainer}>
295
- <div className={getStyles().mainCloseBtnIconOuter}>
306
+ <div className={styles.mainCloseBtnIconContainer}>
307
+ <div className={styles.mainCloseBtnIconOuter}>
296
308
  <TanStackLogo />
297
309
  </div>
298
- <div className={getStyles().mainCloseBtnIconInner}>
310
+ <div className={styles.mainCloseBtnIconInner}>
299
311
  <TanStackLogo />
300
312
  </div>
301
313
  </div>
302
- <div className={getStyles().mainCloseBtnDivider}>-</div>
303
- <div className={getStyles().routerLogoCloseButton}>TanStack Router</div>
314
+ <div className={styles.mainCloseBtnDivider}>-</div>
315
+ <div className={styles.routerLogoCloseButton}>TanStack Router</div>
304
316
  </button>
305
317
  </Container>
306
318
  )
@@ -310,14 +322,18 @@ export const TanStackRouterDevtoolsPanel = React.forwardRef<
310
322
  HTMLDivElement,
311
323
  DevtoolsPanelOptions
312
324
  >(function TanStackRouterDevtoolsPanel(props, ref) {
325
+ const { shadowDOMTarget } = props
326
+
313
327
  return (
314
- <DevtoolsOnCloseContext.Provider
315
- value={{
316
- onCloseClick: () => {},
317
- }}
318
- >
319
- <BaseTanStackRouterDevtoolsPanel ref={ref} {...props} />
320
- </DevtoolsOnCloseContext.Provider>
328
+ <ShadowDomTargetContext.Provider value={shadowDOMTarget}>
329
+ <DevtoolsOnCloseContext.Provider
330
+ value={{
331
+ onCloseClick: () => {},
332
+ }}
333
+ >
334
+ <BaseTanStackRouterDevtoolsPanel ref={ref} {...props} />
335
+ </DevtoolsOnCloseContext.Provider>
336
+ </ShadowDomTargetContext.Provider>
321
337
  )
322
338
  })
323
339
 
@@ -337,6 +353,7 @@ function RouteComp({
337
353
  const routerState = useRouterState({
338
354
  router,
339
355
  } as any)
356
+ const styles = useStyles()
340
357
  const matches = routerState.pendingMatches || routerState.matches
341
358
  const match = routerState.matches.find((d) => d.routeId === route.id)
342
359
 
@@ -369,26 +386,26 @@ function RouteComp({
369
386
  }
370
387
  }}
371
388
  className={cx(
372
- getStyles().routesRowContainer(route.id === activeId, !!match),
389
+ styles.routesRowContainer(route.id === activeId, !!match),
373
390
  )}
374
391
  >
375
392
  <div
376
393
  className={cx(
377
- getStyles().matchIndicator(getRouteStatusColor(matches, route)),
394
+ styles.matchIndicator(getRouteStatusColor(matches, route)),
378
395
  )}
379
396
  />
380
- <div className={cx(getStyles().routesRow(!!match))}>
397
+ <div className={cx(styles.routesRow(!!match))}>
381
398
  <div>
382
- <code className={getStyles().code}>
399
+ <code className={styles.code}>
383
400
  {isRoot ? rootRouteId : route.path || trimPath(route.id)}{' '}
384
401
  </code>
385
- <code className={getStyles().routeParamInfo}>{param}</code>
402
+ <code className={styles.routeParamInfo}>{param}</code>
386
403
  </div>
387
404
  <AgeTicker match={match} router={router} />
388
405
  </div>
389
406
  </div>
390
407
  {route.children?.length ? (
391
- <div className={getStyles().nestedRouteRow(!!isRoot)}>
408
+ <div className={styles.nestedRouteRow(!!isRoot)}>
392
409
  {[...(route.children as Array<Route>)]
393
410
  .sort((a, b) => {
394
411
  return a.rank - b.rank
@@ -417,10 +434,12 @@ const BaseTanStackRouterDevtoolsPanel = React.forwardRef<
417
434
  setIsOpen,
418
435
  handleDragStart,
419
436
  router: userRouter,
437
+ shadowDOMTarget,
420
438
  ...panelProps
421
439
  } = props
422
440
 
423
441
  const { onCloseClick } = useDevtoolsOnClose()
442
+ const styles = useStyles()
424
443
  const { className, ...otherPanelProps } = panelProps
425
444
 
426
445
  const contextRouter = useRouter({ warn: false })
@@ -471,20 +490,17 @@ const BaseTanStackRouterDevtoolsPanel = React.forwardRef<
471
490
  <div
472
491
  ref={ref}
473
492
  className={cx(
474
- getStyles().devtoolsPanel,
493
+ styles.devtoolsPanel,
475
494
  'TanStackRouterDevtoolsPanel',
476
495
  className,
477
496
  )}
478
497
  {...otherPanelProps}
479
498
  >
480
499
  {handleDragStart ? (
481
- <div
482
- className={getStyles().dragHandle}
483
- onMouseDown={handleDragStart}
484
- ></div>
500
+ <div className={styles.dragHandle} onMouseDown={handleDragStart}></div>
485
501
  ) : null}
486
502
  <button
487
- className={getStyles().panelCloseBtn}
503
+ className={styles.panelCloseBtn}
488
504
  onClick={(e) => {
489
505
  setIsOpen(false)
490
506
  onCloseClick(e)
@@ -496,7 +512,7 @@ const BaseTanStackRouterDevtoolsPanel = React.forwardRef<
496
512
  height="6"
497
513
  fill="none"
498
514
  viewBox="0 0 10 6"
499
- className={getStyles().panelCloseBtnIcon}
515
+ className={styles.panelCloseBtnIcon}
500
516
  >
501
517
  <path
502
518
  stroke="currentColor"
@@ -507,8 +523,8 @@ const BaseTanStackRouterDevtoolsPanel = React.forwardRef<
507
523
  ></path>
508
524
  </svg>
509
525
  </button>
510
- <div className={getStyles().firstContainer}>
511
- <div className={getStyles().row}>
526
+ <div className={styles.firstContainer}>
527
+ <div className={styles.row}>
512
528
  <Logo
513
529
  aria-hidden
514
530
  onClick={(e) => {
@@ -517,8 +533,8 @@ const BaseTanStackRouterDevtoolsPanel = React.forwardRef<
517
533
  }}
518
534
  />
519
535
  </div>
520
- <div className={getStyles().routerExplorerContainer}>
521
- <div className={getStyles().routerExplorer}>
536
+ <div className={styles.routerExplorerContainer}>
537
+ <div className={styles.routerExplorer}>
522
538
  <Explorer
523
539
  label="Router"
524
540
  value={Object.fromEntries(
@@ -566,35 +582,33 @@ const BaseTanStackRouterDevtoolsPanel = React.forwardRef<
566
582
  </div>
567
583
  </div>
568
584
  </div>
569
- <div className={getStyles().secondContainer}>
570
- <div className={getStyles().matchesContainer}>
571
- <div className={getStyles().detailsHeader}>
585
+ <div className={styles.secondContainer}>
586
+ <div className={styles.matchesContainer}>
587
+ <div className={styles.detailsHeader}>
572
588
  <span>Pathname</span>
573
589
  {routerState.location.maskedLocation ? (
574
- <div className={getStyles().maskedBadgeContainer}>
575
- <span className={getStyles().maskedBadge}>masked</span>
590
+ <div className={styles.maskedBadgeContainer}>
591
+ <span className={styles.maskedBadge}>masked</span>
576
592
  </div>
577
593
  ) : null}
578
594
  </div>
579
- <div className={getStyles().detailsContent}>
595
+ <div className={styles.detailsContent}>
580
596
  <code>{routerState.location.pathname}</code>
581
597
  {routerState.location.maskedLocation ? (
582
- <code className={getStyles().maskedLocation}>
598
+ <code className={styles.maskedLocation}>
583
599
  {routerState.location.maskedLocation.pathname}
584
600
  </code>
585
601
  ) : null}
586
602
  </div>
587
- <div className={getStyles().detailsHeader}>
588
- <div className={getStyles().routeMatchesToggle}>
603
+ <div className={styles.detailsHeader}>
604
+ <div className={styles.routeMatchesToggle}>
589
605
  <button
590
606
  type="button"
591
607
  onClick={() => {
592
608
  setShowMatches(false)
593
609
  }}
594
610
  disabled={!showMatches}
595
- className={cx(
596
- getStyles().routeMatchesToggleBtn(!showMatches, true),
597
- )}
611
+ className={cx(styles.routeMatchesToggleBtn(!showMatches, true))}
598
612
  >
599
613
  Routes
600
614
  </button>
@@ -605,17 +619,17 @@ const BaseTanStackRouterDevtoolsPanel = React.forwardRef<
605
619
  }}
606
620
  disabled={showMatches}
607
621
  className={cx(
608
- getStyles().routeMatchesToggleBtn(!!showMatches, false),
622
+ styles.routeMatchesToggleBtn(!!showMatches, false),
609
623
  )}
610
624
  >
611
625
  Matches
612
626
  </button>
613
627
  </div>
614
- <div className={getStyles().detailsHeaderInfo}>
628
+ <div className={styles.detailsHeaderInfo}>
615
629
  <div>age / staleTime / gcTime</div>
616
630
  </div>
617
631
  </div>
618
- <div className={cx(getStyles().routesContainer)}>
632
+ <div className={cx(styles.routesContainer)}>
619
633
  {!showMatches ? (
620
634
  <RouteComp
621
635
  router={router}
@@ -636,18 +650,16 @@ const BaseTanStackRouterDevtoolsPanel = React.forwardRef<
636
650
  onClick={() =>
637
651
  setActiveId(activeId === match.id ? '' : match.id)
638
652
  }
639
- className={cx(
640
- getStyles().matchRow(match === activeMatch),
641
- )}
653
+ className={cx(styles.matchRow(match === activeMatch))}
642
654
  >
643
655
  <div
644
656
  className={cx(
645
- getStyles().matchIndicator(getStatusColor(match)),
657
+ styles.matchIndicator(getStatusColor(match)),
646
658
  )}
647
659
  />
648
660
 
649
661
  <code
650
- className={getStyles().matchID}
662
+ className={styles.matchID}
651
663
  >{`${match.routeId === rootRouteId ? rootRouteId : match.pathname}`}</code>
652
664
  <AgeTicker match={match} router={router} />
653
665
  </div>
@@ -659,10 +671,10 @@ const BaseTanStackRouterDevtoolsPanel = React.forwardRef<
659
671
  </div>
660
672
  </div>
661
673
  {routerState.cachedMatches.length ? (
662
- <div className={getStyles().cachedMatchesContainer}>
663
- <div className={getStyles().detailsHeader}>
674
+ <div className={styles.cachedMatchesContainer}>
675
+ <div className={styles.detailsHeader}>
664
676
  <div>Cached Matches</div>
665
- <div className={getStyles().detailsHeaderInfo}>
677
+ <div className={styles.detailsHeaderInfo}>
666
678
  age / staleTime / gcTime
667
679
  </div>
668
680
  </div>
@@ -676,15 +688,15 @@ const BaseTanStackRouterDevtoolsPanel = React.forwardRef<
676
688
  onClick={() =>
677
689
  setActiveId(activeId === match.id ? '' : match.id)
678
690
  }
679
- className={cx(getStyles().matchRow(match === activeMatch))}
691
+ className={cx(styles.matchRow(match === activeMatch))}
680
692
  >
681
693
  <div
682
694
  className={cx(
683
- getStyles().matchIndicator(getStatusColor(match)),
695
+ styles.matchIndicator(getStatusColor(match)),
684
696
  )}
685
697
  />
686
698
 
687
- <code className={getStyles().matchID}>{`${match.id}`}</code>
699
+ <code className={styles.matchID}>{`${match.id}`}</code>
688
700
 
689
701
  <AgeTicker match={match} router={router} />
690
702
  </div>
@@ -695,12 +707,12 @@ const BaseTanStackRouterDevtoolsPanel = React.forwardRef<
695
707
  ) : null}
696
708
  </div>
697
709
  {activeMatch ? (
698
- <div className={getStyles().thirdContainer}>
699
- <div className={getStyles().detailsHeader}>Match Details</div>
710
+ <div className={styles.thirdContainer}>
711
+ <div className={styles.detailsHeader}>Match Details</div>
700
712
  <div>
701
- <div className={getStyles().matchDetails}>
713
+ <div className={styles.matchDetails}>
702
714
  <div
703
- className={getStyles().matchStatus(
715
+ className={styles.matchStatus(
704
716
  activeMatch.status,
705
717
  activeMatch.isFetching,
706
718
  )}
@@ -711,15 +723,15 @@ const BaseTanStackRouterDevtoolsPanel = React.forwardRef<
711
723
  : activeMatch.status}
712
724
  </div>
713
725
  </div>
714
- <div className={getStyles().matchDetailsInfoLabel}>
726
+ <div className={styles.matchDetailsInfoLabel}>
715
727
  <div>ID:</div>
716
- <div className={getStyles().matchDetailsInfo}>
728
+ <div className={styles.matchDetailsInfo}>
717
729
  <code>{activeMatch.id}</code>
718
730
  </div>
719
731
  </div>
720
- <div className={getStyles().matchDetailsInfoLabel}>
732
+ <div className={styles.matchDetailsInfoLabel}>
721
733
  <div>State:</div>
722
- <div className={getStyles().matchDetailsInfo}>
734
+ <div className={styles.matchDetailsInfo}>
723
735
  {routerState.pendingMatches?.find(
724
736
  (d) => d.id === activeMatch.id,
725
737
  )
@@ -729,9 +741,9 @@ const BaseTanStackRouterDevtoolsPanel = React.forwardRef<
729
741
  : 'Cached'}
730
742
  </div>
731
743
  </div>
732
- <div className={getStyles().matchDetailsInfoLabel}>
744
+ <div className={styles.matchDetailsInfoLabel}>
733
745
  <div>Last Updated:</div>
734
- <div className={getStyles().matchDetailsInfo}>
746
+ <div className={styles.matchDetailsInfo}>
735
747
  {activeMatch.updatedAt
736
748
  ? new Date(activeMatch.updatedAt).toLocaleTimeString()
737
749
  : 'N/A'}
@@ -741,8 +753,8 @@ const BaseTanStackRouterDevtoolsPanel = React.forwardRef<
741
753
  </div>
742
754
  {activeMatch.loaderData ? (
743
755
  <>
744
- <div className={getStyles().detailsHeader}>Loader Data</div>
745
- <div className={getStyles().detailsContent}>
756
+ <div className={styles.detailsHeader}>Loader Data</div>
757
+ <div className={styles.detailsContent}>
746
758
  <Explorer
747
759
  label="loaderData"
748
760
  value={activeMatch.loaderData}
@@ -751,16 +763,16 @@ const BaseTanStackRouterDevtoolsPanel = React.forwardRef<
751
763
  </div>
752
764
  </>
753
765
  ) : null}
754
- <div className={getStyles().detailsHeader}>Explorer</div>
755
- <div className={getStyles().detailsContent}>
766
+ <div className={styles.detailsHeader}>Explorer</div>
767
+ <div className={styles.detailsContent}>
756
768
  <Explorer label="Match" value={activeMatch} defaultExpanded={{}} />
757
769
  </div>
758
770
  </div>
759
771
  ) : null}
760
772
  {hasSearch ? (
761
- <div className={getStyles().fourthContainer}>
762
- <div className={getStyles().detailsHeader}>Search Params</div>
763
- <div className={getStyles().detailsContent}>
773
+ <div className={styles.fourthContainer}>
774
+ <div className={styles.detailsHeader}>Search Params</div>
775
+ <div className={styles.detailsContent}>
764
776
  <Explorer
765
777
  value={routerState.location.search}
766
778
  defaultExpanded={Object.keys(routerState.location.search).reduce(
@@ -785,6 +797,7 @@ function AgeTicker({
785
797
  match?: AnyRouteMatch
786
798
  router: AnyRouter
787
799
  }) {
800
+ const styles = useStyles()
788
801
  const rerender = React.useReducer(
789
802
  () => ({}),
790
803
  () => ({}),
@@ -817,7 +830,7 @@ function AgeTicker({
817
830
  route.options.gcTime ?? router.options.defaultGcTime ?? 30 * 60 * 1000
818
831
 
819
832
  return (
820
- <div className={cx(getStyles().ageTicker(age > staleTime))}>
833
+ <div className={cx(styles.ageTicker(age > staleTime))}>
821
834
  <div>{formatTime(age)}</div>
822
835
  <div>/</div>
823
836
  <div>{formatTime(staleTime)}</div>
@@ -846,9 +859,12 @@ function formatTime(ms: number) {
846
859
  return formatter.format(values[chosenUnitIndex]!) + units[chosenUnitIndex]
847
860
  }
848
861
 
849
- const stylesFactory = () => {
862
+ const stylesFactory = (shadowDOMTarget?: ShadowRoot) => {
850
863
  const { colors, font, size, alpha, shadow, border } = tokens
851
864
  const { fontFamily, lineHeight, size: fontSize } = font
865
+ const css = shadowDOMTarget
866
+ ? goober.css.bind({ target: shadowDOMTarget })
867
+ : goober.css
852
868
 
853
869
  return {
854
870
  devtoolsPanelContainer: css`
@@ -1420,9 +1436,10 @@ const stylesFactory = () => {
1420
1436
 
1421
1437
  let _styles: ReturnType<typeof stylesFactory> | null = null
1422
1438
 
1423
- function getStyles() {
1439
+ function useStyles() {
1440
+ const shadowDomTarget = React.useContext(ShadowDomTargetContext)
1424
1441
  if (_styles) return _styles
1425
- _styles = stylesFactory()
1442
+ _styles = stylesFactory(shadowDomTarget)
1426
1443
 
1427
1444
  return _styles
1428
1445
  }