@tanstack/router-devtools 0.0.1-beta.278 → 0.0.1-beta.280

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.
@@ -534,6 +534,25 @@
534
534
  throw error;
535
535
  }));
536
536
  }
537
+ function multiSortBy(arr, accessors = [d => d]) {
538
+ return arr.map((d, i) => [d, i]).sort(([a, ai], [b, bi]) => {
539
+ for (const accessor of accessors) {
540
+ const ao = accessor(a);
541
+ const bo = accessor(b);
542
+ if (typeof ao === 'undefined') {
543
+ if (typeof bo === 'undefined') {
544
+ continue;
545
+ }
546
+ return 1;
547
+ }
548
+ if (ao === bo) {
549
+ continue;
550
+ }
551
+ return ao > bo ? 1 : -1;
552
+ }
553
+ return ai - bi;
554
+ }).map(([d]) => d);
555
+ }
537
556
 
538
557
  const Panel = styled('div', (_props, theme) => ({
539
558
  fontSize: 'clamp(12px, 1.5vw, 14px)',
@@ -1007,8 +1026,8 @@
1007
1026
  function RouteComp({
1008
1027
  route,
1009
1028
  isRoot,
1010
- activeRouteId,
1011
- setActiveRouteId
1029
+ activeId,
1030
+ setActiveId
1012
1031
  }) {
1013
1032
  const routerState = useRouterState();
1014
1033
  const matches = routerState.status === 'pending' ? routerState.pendingMatches ?? [] : routerState.matches;
@@ -1018,7 +1037,7 @@
1018
1037
  "aria-label": `Open match details for ${route.id}`,
1019
1038
  onClick: () => {
1020
1039
  if (match) {
1021
- setActiveRouteId(activeRouteId === route.id ? '' : route.id);
1040
+ setActiveId(activeId === route.id ? '' : route.id);
1022
1041
  }
1023
1042
  },
1024
1043
  style: {
@@ -1026,14 +1045,15 @@
1026
1045
  borderBottom: `solid 1px ${defaultTheme.grayAlt}`,
1027
1046
  cursor: match ? 'pointer' : 'default',
1028
1047
  alignItems: 'center',
1029
- background: route.id === activeRouteId ? 'rgba(255,255,255,.1)' : undefined
1048
+ background: route.id === activeId ? 'rgba(255,255,255,.1)' : undefined,
1049
+ padding: '.25rem .5rem',
1050
+ gap: '.5rem'
1030
1051
  }
1031
1052
  }, isRoot ? null : /*#__PURE__*/React.createElement("div", {
1032
1053
  style: {
1033
1054
  flex: '0 0 auto',
1034
1055
  width: '.7rem',
1035
1056
  height: '.7rem',
1036
- margin: '.5rem .75rem',
1037
1057
  alignItems: 'center',
1038
1058
  justifyContent: 'center',
1039
1059
  fontWeight: 'bold',
@@ -1042,21 +1062,29 @@
1042
1062
  background: getRouteStatusColor(matches, route, defaultTheme),
1043
1063
  opacity: match ? 1 : 0.3
1044
1064
  }
1045
- }), /*#__PURE__*/React.createElement(Code, {
1065
+ }), /*#__PURE__*/React.createElement("div", {
1046
1066
  style: {
1047
1067
  flex: '1 0 auto',
1048
1068
  display: 'flex',
1049
1069
  justifyContent: 'space-between',
1050
- padding: '.25rem .5rem .25rem 0',
1051
- paddingLeft: isRoot ? '.5rem' : 0,
1070
+ alignItems: 'center',
1071
+ padding: isRoot ? '0 .25rem' : 0,
1052
1072
  opacity: match ? 1 : 0.7,
1053
1073
  fontSize: '0.7rem'
1054
1074
  }
1055
- }, /*#__PURE__*/React.createElement("span", null, route.path || trimPath(route.id), " "), match ? /*#__PURE__*/React.createElement("span", {
1075
+ }, /*#__PURE__*/React.createElement(Code, null, route.path || trimPath(route.id), " "), /*#__PURE__*/React.createElement("div", {
1076
+ style: {
1077
+ display: 'flex',
1078
+ alignItems: 'center',
1079
+ gap: '.5rem'
1080
+ }
1081
+ }, match ? /*#__PURE__*/React.createElement(Code, {
1056
1082
  style: {
1057
1083
  opacity: 0.3
1058
1084
  }
1059
- }, match.id) : null)), route.children?.length ? /*#__PURE__*/React.createElement("div", {
1085
+ }, match.id) : null, /*#__PURE__*/React.createElement(AgeTicker, {
1086
+ match: match
1087
+ })))), route.children?.length ? /*#__PURE__*/React.createElement("div", {
1060
1088
  style: {
1061
1089
  marginLeft: isRoot ? 0 : '1rem',
1062
1090
  borderLeft: isRoot ? '' : `solid 1px ${defaultTheme.grayAlt}`
@@ -1066,8 +1094,8 @@
1066
1094
  }).map(r => /*#__PURE__*/React.createElement(RouteComp, {
1067
1095
  key: r.id,
1068
1096
  route: r,
1069
- activeRouteId: activeRouteId,
1070
- setActiveRouteId: setActiveRouteId
1097
+ activeId: activeId,
1098
+ setActiveId: setActiveId
1071
1099
  }))) : null);
1072
1100
  }
1073
1101
  const TanStackRouterDevtoolsPanel = /*#__PURE__*/React.forwardRef(function TanStackRouterDevtoolsPanel(props, ref) {
@@ -1080,31 +1108,19 @@
1080
1108
  } = props;
1081
1109
  const router = useRouter();
1082
1110
  const routerState = useRouterState();
1083
- const matches = [...(routerState.pendingMatches ?? []), ...routerState.matches];
1111
+ const matches = [...(routerState.pendingMatches ?? []), ...routerState.matches, ...routerState.cachedMatches];
1084
1112
  invariant(router, '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.');
1085
1113
 
1086
1114
  // useStore(router.__store)
1087
1115
 
1088
1116
  const [showMatches, setShowMatches] = useLocalStorage('tanstackRouterDevtoolsShowMatches', true);
1089
- const [activeRouteId, setActiveRouteId] = useLocalStorage('tanstackRouterDevtoolsActiveRouteId', '');
1090
- const activeMatch = React.useMemo(() => matches.find(d => d.routeId === activeRouteId), [matches, activeRouteId]);
1117
+ const [activeId, setActiveId] = useLocalStorage('tanstackRouterDevtoolsActiveRouteId', '');
1118
+ const activeMatch = React.useMemo(() => matches.find(d => d.routeId === activeId || d.id === activeId), [matches, activeId]);
1091
1119
  const hasSearch = Object.keys(routerState.location.search || {}).length;
1092
-
1093
- // const preloadMatches = matches.filter((match) => {
1094
- // return (
1095
- // !state.matchIds.includes(match.id) &&
1096
- // !state.pendingMatchIds.includes(match.id)
1097
- // )
1098
- // })
1099
-
1100
- // React.useEffect(() => {
1101
- // const interval = setInterval(() => {
1102
- // router.cleanMatches()
1103
- // }, 1000)
1104
-
1105
- // return () => clearInterval(interval)
1106
- // }, [router])
1107
-
1120
+ const explorerState = {
1121
+ ...router,
1122
+ state: router.state
1123
+ };
1108
1124
  return /*#__PURE__*/React.createElement(ThemeProvider, {
1109
1125
  theme: defaultTheme
1110
1126
  }, /*#__PURE__*/React.createElement(Panel, _extends({
@@ -1209,10 +1225,11 @@
1209
1225
  }
1210
1226
  }, /*#__PURE__*/React.createElement(Explorer, {
1211
1227
  label: "Router",
1212
- value: router,
1228
+ value: Object.fromEntries(multiSortBy(Object.keys(explorerState), ['state', 'routesById', 'routesByPath', 'flatRoutes', 'options'].map(d => dd => dd !== d)).map(key => [key, explorerState[key]]).filter(d => typeof d[1] !== 'function' && !['__store', 'basepath', 'injectedHtml', 'subscribers', 'latestLoadPromise', 'navigateTimeout', 'resetNextScroll', 'tempLocationKey', 'latestLocation', 'routeTree', 'history'].includes(d[0]))),
1213
1229
  defaultExpanded: {
1214
1230
  state: {},
1215
- context: {}
1231
+ context: {},
1232
+ options: {}
1216
1233
  },
1217
1234
  filterSubEntries: subEntries => {
1218
1235
  return subEntries.filter(d => typeof d.value !== 'function');
@@ -1276,9 +1293,16 @@
1276
1293
  zIndex: 1,
1277
1294
  display: 'flex',
1278
1295
  alignItems: 'center',
1296
+ justifyContent: 'space-between',
1279
1297
  gap: '.5rem',
1280
1298
  fontWeight: 'bold'
1281
1299
  }
1300
+ }, /*#__PURE__*/React.createElement("div", {
1301
+ style: {
1302
+ display: 'flex',
1303
+ alignItems: 'center',
1304
+ gap: '.5rem'
1305
+ }
1282
1306
  }, /*#__PURE__*/React.createElement("button", {
1283
1307
  type: "button",
1284
1308
  onClick: () => {
@@ -1307,17 +1331,23 @@
1307
1331
  color: 'inherit',
1308
1332
  cursor: 'pointer'
1309
1333
  }
1310
- }, "Matches")), !showMatches ? /*#__PURE__*/React.createElement(RouteComp, {
1334
+ }, "Matches")), /*#__PURE__*/React.createElement("div", {
1335
+ style: {
1336
+ opacity: 0.3,
1337
+ fontSize: '0.7rem',
1338
+ fontWeight: 'normal'
1339
+ }
1340
+ }, "age / staleTime / gcTime")), !showMatches ? /*#__PURE__*/React.createElement(RouteComp, {
1311
1341
  route: router.routeTree,
1312
1342
  isRoot: true,
1313
- activeRouteId: activeRouteId,
1314
- setActiveRouteId: setActiveRouteId
1343
+ activeId: activeId,
1344
+ setActiveId: setActiveId
1315
1345
  }) : /*#__PURE__*/React.createElement("div", null, (routerState.status === 'pending' ? routerState.pendingMatches ?? [] : routerState.matches).map((match, i) => {
1316
1346
  return /*#__PURE__*/React.createElement("div", {
1317
- key: match.routeId || i,
1347
+ key: match.id || i,
1318
1348
  role: "button",
1319
- "aria-label": `Open match details for ${match.routeId}`,
1320
- onClick: () => setActiveRouteId(activeRouteId === match.routeId ? '' : match.routeId),
1349
+ "aria-label": `Open match details for ${match.id}`,
1350
+ onClick: () => setActiveId(activeId === match.id ? '' : match.id),
1321
1351
  style: {
1322
1352
  display: 'flex',
1323
1353
  borderBottom: `solid 1px ${defaultTheme.grayAlt}`,
@@ -1343,8 +1373,73 @@
1343
1373
  padding: '.5em',
1344
1374
  fontSize: '0.7rem'
1345
1375
  }
1346
- }, `${match.id}`));
1347
- })))), activeMatch ? /*#__PURE__*/React.createElement(ActivePanel, null, /*#__PURE__*/React.createElement("div", {
1376
+ }, `${match.id}`), /*#__PURE__*/React.createElement(AgeTicker, {
1377
+ match: match
1378
+ }));
1379
+ }))), routerState.cachedMatches?.length ? /*#__PURE__*/React.createElement("div", {
1380
+ style: {
1381
+ flex: '1 1 auto',
1382
+ overflowY: 'auto',
1383
+ maxHeight: '50%'
1384
+ }
1385
+ }, /*#__PURE__*/React.createElement("div", {
1386
+ style: {
1387
+ padding: '.5em',
1388
+ background: defaultTheme.backgroundAlt,
1389
+ position: 'sticky',
1390
+ top: 0,
1391
+ zIndex: 1,
1392
+ display: 'flex',
1393
+ alignItems: 'center',
1394
+ justifyContent: 'space-between',
1395
+ gap: '.5rem',
1396
+ fontWeight: 'bold'
1397
+ }
1398
+ }, /*#__PURE__*/React.createElement("div", null, "Cached Matches"), /*#__PURE__*/React.createElement("div", {
1399
+ style: {
1400
+ opacity: 0.3,
1401
+ fontSize: '0.7rem',
1402
+ fontWeight: 'normal'
1403
+ }
1404
+ }, "age / staleTime / gcTime")), /*#__PURE__*/React.createElement("div", null, routerState.cachedMatches.map(match => {
1405
+ return /*#__PURE__*/React.createElement("div", {
1406
+ key: match.id,
1407
+ role: "button",
1408
+ "aria-label": `Open match details for ${match.id}`,
1409
+ onClick: () => setActiveId(activeId === match.id ? '' : match.id),
1410
+ style: {
1411
+ display: 'flex',
1412
+ borderBottom: `solid 1px ${defaultTheme.grayAlt}`,
1413
+ cursor: 'pointer',
1414
+ alignItems: 'center',
1415
+ background: match === activeMatch ? 'rgba(255,255,255,.1)' : undefined,
1416
+ fontSize: '0.7rem'
1417
+ }
1418
+ }, /*#__PURE__*/React.createElement("div", {
1419
+ style: {
1420
+ flex: '0 0 auto',
1421
+ width: '.75rem',
1422
+ height: '.75rem',
1423
+ marginLeft: '.25rem',
1424
+ background: getStatusColor(match, defaultTheme),
1425
+ alignItems: 'center',
1426
+ justifyContent: 'center',
1427
+ fontWeight: 'bold',
1428
+ borderRadius: '100%',
1429
+ transition: 'all 1s ease-out'
1430
+ }
1431
+ }), /*#__PURE__*/React.createElement(Code, {
1432
+ style: {
1433
+ padding: '.5em'
1434
+ }
1435
+ }, `${match.id}`), /*#__PURE__*/React.createElement("div", {
1436
+ style: {
1437
+ marginLeft: 'auto'
1438
+ }
1439
+ }, /*#__PURE__*/React.createElement(AgeTicker, {
1440
+ match: match
1441
+ })));
1442
+ }))) : null), activeMatch ? /*#__PURE__*/React.createElement(ActivePanel, null, /*#__PURE__*/React.createElement("div", {
1348
1443
  style: {
1349
1444
  padding: '.5em',
1350
1445
  background: defaultTheme.backgroundAlt,
@@ -1353,7 +1448,11 @@
1353
1448
  bottom: 0,
1354
1449
  zIndex: 1
1355
1450
  }
1356
- }, "Match Details"), /*#__PURE__*/React.createElement("div", null, /*#__PURE__*/React.createElement("table", null, /*#__PURE__*/React.createElement("tbody", null, /*#__PURE__*/React.createElement("tr", null, /*#__PURE__*/React.createElement("td", {
1451
+ }, "Match Details"), /*#__PURE__*/React.createElement("div", null, /*#__PURE__*/React.createElement("table", {
1452
+ style: {
1453
+ fontSize: '0.8rem'
1454
+ }
1455
+ }, /*#__PURE__*/React.createElement("tbody", null, /*#__PURE__*/React.createElement("tr", null, /*#__PURE__*/React.createElement("td", {
1357
1456
  style: {
1358
1457
  opacity: '.5'
1359
1458
  }
@@ -1365,7 +1464,7 @@
1365
1464
  style: {
1366
1465
  opacity: '.5'
1367
1466
  }
1368
- }, "Status"), /*#__PURE__*/React.createElement("td", null, activeMatch.status)), /*#__PURE__*/React.createElement("tr", null, /*#__PURE__*/React.createElement("td", {
1467
+ }, "Status"), /*#__PURE__*/React.createElement("td", null, routerState.pendingMatches?.find(d => d.id === activeMatch.id) ? 'Pending' : routerState.matches?.find(d => d.id === activeMatch.id) ? 'Active' : 'Cached', ' ', "- ", activeMatch.status)), /*#__PURE__*/React.createElement("tr", null, /*#__PURE__*/React.createElement("td", {
1369
1468
  style: {
1370
1469
  opacity: '.5'
1371
1470
  }
@@ -1435,6 +1534,55 @@
1435
1534
  }, {})
1436
1535
  }))) : null));
1437
1536
  });
1537
+ function AgeTicker({
1538
+ match
1539
+ }) {
1540
+ const router = useRouter();
1541
+ const rerender = React.useReducer(() => ({}), () => ({}))[1];
1542
+ React.useEffect(() => {
1543
+ const interval = setInterval(() => {
1544
+ rerender();
1545
+ }, 1000);
1546
+ return () => {
1547
+ clearInterval(interval);
1548
+ };
1549
+ }, []);
1550
+ if (!match) {
1551
+ return null;
1552
+ }
1553
+ const route = router.looseRoutesById[match?.routeId];
1554
+ if (!route.options.loader) {
1555
+ return null;
1556
+ }
1557
+ const age = Date.now() - match?.updatedAt;
1558
+ const staleTime = route.options.staleTime ?? router.options.defaultStaleTime ?? 0;
1559
+ const gcTime = route.options.gcTime ?? router.options.defaultGcTime ?? 30 * 60 * 1000;
1560
+ return /*#__PURE__*/React.createElement("div", {
1561
+ style: {
1562
+ display: 'inline-flex',
1563
+ alignItems: 'center',
1564
+ gap: '.25rem',
1565
+ color: age > staleTime ? defaultTheme.warning : undefined
1566
+ }
1567
+ }, /*#__PURE__*/React.createElement("div", {
1568
+ style: {}
1569
+ }, formatTime(age)), /*#__PURE__*/React.createElement("div", null, "/"), /*#__PURE__*/React.createElement("div", null, formatTime(staleTime)), /*#__PURE__*/React.createElement("div", null, "/"), /*#__PURE__*/React.createElement("div", null, formatTime(gcTime)));
1570
+ }
1571
+ function formatTime(ms) {
1572
+ const units = ['s', 'min', 'h', 'd'];
1573
+ const values = [ms / 1000, ms / 60000, ms / 3600000, ms / 86400000];
1574
+ let chosenUnitIndex = 0;
1575
+ for (let i = 1; i < values.length; i++) {
1576
+ if (values[i] < 1) break;
1577
+ chosenUnitIndex = i;
1578
+ }
1579
+ const formatter = new Intl.NumberFormat(navigator.language, {
1580
+ compactDisplay: 'short',
1581
+ notation: 'compact',
1582
+ maximumFractionDigits: 0
1583
+ });
1584
+ return formatter.format(values[chosenUnitIndex]) + units[chosenUnitIndex];
1585
+ }
1438
1586
 
1439
1587
  exports.TanStackRouterDevtools = TanStackRouterDevtools;
1440
1588
  exports.TanStackRouterDevtoolsPanel = TanStackRouterDevtoolsPanel;