@tanstack/router-core 1.171.18 → 1.171.19

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.
@@ -108,7 +108,7 @@ function navigateFrom(router, location) {
108
108
  _fromLocation: location
109
109
  });
110
110
  }
111
- async function contextualize(router, lane, options, end, planSuccessfulLane) {
111
+ async function contextualize(router, lane, options, end, planSuccessfulLane, retainedEnd) {
112
112
  const [location, matches] = lane;
113
113
  const signal = options[0].signal;
114
114
  const preload = !!options[4];
@@ -163,7 +163,7 @@ async function contextualize(router, lane, options, end, planSuccessfulLane) {
163
163
  ...router.options.additionalContext
164
164
  };
165
165
  const previousStatus = match.status;
166
- if (previousStatus === "success") match.status = "pending";
166
+ if (previousStatus === "success" && index >= retainedEnd) match.status = "pending";
167
167
  options[8]?.();
168
168
  try {
169
169
  setFetching(router, match, "beforeLoad", options[0]);
@@ -209,22 +209,12 @@ function releaseFlight(router, match) {
209
209
  * Not passing in a `next` ownership recipient
210
210
  * is equivalent to discarding the match resources
211
211
  */
212
- function transferMatchResources(router, previous, next) {
212
+ function transferMatchResources(router, previous, next, deferSameIdFlight) {
213
213
  const abort = [];
214
214
  for (const match of previous) if (!next?.includes(match)) {
215
215
  const flight = match._flight;
216
216
  match._flight = void 0;
217
- const controller = releaseOwnedFlight(router, match, flight);
218
- if (controller) abort.push(controller);
219
- }
220
- for (const controller of abort) controller.abort();
221
- }
222
- function transferPredecessorResources(router, previous, next) {
223
- const abort = [];
224
- for (const match of previous) if (!next.includes(match)) {
225
- const flight = match._flight;
226
- match._flight = void 0;
227
- if (flight?.[2] === 1 && router._flights?.get(match.id) === flight && !(process.env.NODE_ENV !== "production" && router._tx?.[6]) && next.some((candidate) => candidate.id === match.id)) flight[2] = 0;
217
+ if (deferSameIdFlight && flight?.[2] === 1 && router._flights?.get(match.id) === flight && !(process.env.NODE_ENV !== "production" && router._tx?.[6]) && next?.some((candidate) => candidate.id === match.id)) flight[2] = 0;
228
218
  else {
229
219
  const controller = releaseOwnedFlight(router, match, flight);
230
220
  if (controller) abort.push(controller);
@@ -232,14 +222,6 @@ function transferPredecessorResources(router, previous, next) {
232
222
  }
233
223
  for (const controller of abort) controller.abort();
234
224
  }
235
- function releaseUnownedFlights(router) {
236
- const abort = [];
237
- for (const [id, flight] of router._flights ?? []) if (!flight[2]) {
238
- router._flights.delete(id);
239
- abort.push(flight[1]);
240
- }
241
- for (const controller of abort) controller.abort();
242
- }
243
225
  function acquireMatchResources(matches) {
244
226
  for (const match of matches) {
245
227
  const flight = match._flight;
@@ -340,7 +322,7 @@ function getParentSnapshot(match, outcome) {
340
322
  };
341
323
  return match;
342
324
  }
343
- function createLoaderTask(router, lane, index, tasks, semanticParent, options) {
325
+ function createLoaderTask(router, lane, index, tasks, semanticParent, options, retainedEnd) {
344
326
  const match = lane[1][index];
345
327
  const route = getRoute(router, match);
346
328
  const preload = !!options[4];
@@ -386,7 +368,7 @@ function createLoaderTask(router, lane, index, tasks, semanticParent, options) {
386
368
  const acceptedFlight = match._flight;
387
369
  match._flight = donor;
388
370
  releaseOwnedFlight(router, match, acceptedFlight)?.abort();
389
- if (match.status === "success") match.status = "pending";
371
+ if (match.status === "success" && index >= retainedEnd) match.status = "pending";
390
372
  options[8]?.();
391
373
  }
392
374
  if (!loaded) match.isFetching = false;
@@ -536,8 +518,9 @@ async function reduceLane(router, lane, tasks, controller, redirects, settlement
536
518
  }
537
519
  };
538
520
  install();
521
+ const route = getRoute(router, match);
539
522
  try {
540
- await waitFor(outcome ? Promise.resolve().then(() => loadRouteChunk(getRoute(router, match), kind === ERROR ? "errorComponent" : "notFoundComponent")) : Promise.all([loadRouteChunk(getRoute(router, match)), loadRouteChunk(getRoute(router, match), "notFoundComponent")]), controller.signal);
523
+ await waitFor(outcome ? Promise.resolve().then(() => loadRouteChunk(route, kind === ERROR ? "errorComponent" : "notFoundComponent")) : Promise.all([loadRouteChunk(route), loadRouteChunk(route, "notFoundComponent")]), controller.signal);
541
524
  } catch (cause) {
542
525
  if (cause === controller.signal) {
543
526
  discardBackground(router, lane);
@@ -590,6 +573,7 @@ async function projectLane(router, lane, signal, start = 0, end = lane[1].length
590
573
  }
591
574
  async function executeClientLane(router, location, matches, options) {
592
575
  const matched = [location, matches];
576
+ const presented = router.stores.matches.get();
593
577
  let plannedBoundary = matches.findIndex((match) => match._notFound);
594
578
  if (router.options.notFoundMode !== "root" && plannedBoundary >= 0) {
595
579
  const boundary = await getNotFoundBoundary(router, matched[1], void 0, options[0].signal, plannedBoundary);
@@ -600,16 +584,24 @@ async function executeClientLane(router, location, matches, options) {
600
584
  plannedBoundary = boundary;
601
585
  }
602
586
  let end = plannedBoundary < 0 ? matches.length : plannedBoundary + 1;
587
+ let retainedEnd = 0;
588
+ while (retainedEnd < end && retainedEnd !== plannedBoundary) {
589
+ const match = matches[retainedEnd];
590
+ const committed = options[3][retainedEnd];
591
+ const visible = presented[retainedEnd];
592
+ if (committed?.id !== match.id || committed.status !== "success" || committed._notFound || match.preload || visible?.id !== match.id || visible.status !== "success" || visible._notFound) break;
593
+ retainedEnd++;
594
+ }
603
595
  const tasks = [];
604
596
  const start = options[7] ?? 0;
605
597
  let semanticParent = start ? Promise.resolve(matched[1][start - 1]) : void 0;
606
598
  const planSuccessfulLane = () => {
607
599
  for (let index = start; index < end; index++) {
608
600
  if (options[0].signal.aborted) break;
609
- semanticParent = createLoaderTask(router, matched, index, tasks, semanticParent, options);
601
+ semanticParent = createLoaderTask(router, matched, index, tasks, semanticParent, options, retainedEnd);
610
602
  }
611
603
  };
612
- const failure = await contextualize(router, matched, options, end, planSuccessfulLane);
604
+ const failure = await contextualize(router, matched, options, end, planSuccessfulLane, retainedEnd);
613
605
  if (failure) {
614
606
  options[5] = true;
615
607
  end = failure[0];
@@ -619,7 +611,14 @@ async function executeClientLane(router, location, matches, options) {
619
611
  } else if (failure[1][0] >= REDIRECTED) end = 0;
620
612
  planSuccessfulLane();
621
613
  }
622
- if (options[2]() && !options[4]) releaseUnownedFlights(router);
614
+ if (options[2]() && !options[4]) {
615
+ const abort = [];
616
+ for (const [id, flight] of router._flights ?? []) if (!flight[2]) {
617
+ router._flights.delete(id);
618
+ abort.push(flight[1]);
619
+ }
620
+ for (const controller of abort) controller.abort();
621
+ }
623
622
  let reduced;
624
623
  try {
625
624
  const reduction = reduceLane(router, matched, tasks, options[0], options[1], settleTasks(tasks, failure, matched[2]), options[8]);
@@ -633,30 +632,6 @@ async function executeClientLane(router, location, matches, options) {
633
632
  return projectLane(router, reduced, options[0].signal, options[7] === reduced[1].length ? options[7] : 0);
634
633
  }
635
634
  /**
636
- * Finds the first route that should show pending UI and its two timing values.
637
- * A fallback already on screen remains selected after its route loads, so we
638
- * do not jump to a child fallback. Matches put back into pending by invalidation
639
- * skip pendingMs, and a route without a usable fallback blocks pending UI for deeper routes.
640
- */
641
- function pendingConfig(router, matches) {
642
- const presented = router.stores.matches.get();
643
- for (let index = 0; index < matches.length; index++) {
644
- const match = matches[index];
645
- const success = match.status === "success";
646
- const visible = success && presented[index]?.id === match.id && presented[index]?.status === "pending";
647
- if (success && !visible) continue;
648
- const route = getRoute(router, match);
649
- const delay = visible || match.invalid ? 0 : route.options.pendingMs ?? router.options.defaultPendingMs;
650
- const component = route.options.pendingComponent ?? router.options.defaultPendingComponent;
651
- return component && typeof delay === "number" && delay !== Infinity ? [
652
- delay,
653
- index,
654
- route.options.pendingMinMs ?? router.options.defaultPendingMinMs ?? 0,
655
- component
656
- ] : void 0;
657
- }
658
- }
659
- /**
660
635
  * Waits for `pendingMs`, then presents the complete lane. Rendering applies the
661
636
  * selected boundary cutoff while retaining every match's structural state.
662
637
  * A replacement load for the same match keeps the timer; choosing a different
@@ -674,20 +649,36 @@ function offerPending(router, tx) {
674
649
  clearTimeout(session?.[3]);
675
650
  router._pending = session = void 0;
676
651
  }
677
- const config = pendingConfig(router, tx[3]);
678
- if (!config) return;
679
- const [delay, boundary, min, component] = config;
680
- const matchId = tx[3][boundary].id;
652
+ const matches = tx[3];
653
+ const presented = router.stores.matches.get();
654
+ let boundary = -1;
655
+ let delay;
656
+ let min;
657
+ let component;
658
+ let presentedPending = false;
659
+ for (let index = 0; index < matches.length; index++) {
660
+ const match = matches[index];
661
+ const success = match.status === "success";
662
+ presentedPending = presented[index]?.id === match.id && presented[index]?.status === "pending";
663
+ if (success && !presentedPending) continue;
664
+ const route = getRoute(router, match);
665
+ delay = success && presentedPending || match.invalid ? 0 : route.options.pendingMs ?? router.options.defaultPendingMs;
666
+ component = route.options.pendingComponent ?? router.options.defaultPendingComponent;
667
+ if (!component || typeof delay !== "number" || delay === Infinity) return;
668
+ boundary = index;
669
+ min = route.options.pendingMinMs ?? router.options.defaultPendingMinMs ?? 0;
670
+ break;
671
+ }
672
+ if (boundary < 0) return;
673
+ const matchId = matches[boundary].id;
681
674
  if (!session || session[1] !== boundary || sessionMatchId !== matchId) {
682
675
  clearTimeout(session?.[3]);
683
- const presented = router.stores.matches.get()[boundary];
684
- const visible = presented?.id === matchId && presented.status === "pending";
685
676
  router._pending = session = [
686
677
  tx,
687
678
  boundary,
688
- visible ? Date.now() + min : tx[4] + delay,
679
+ presentedPending ? Date.now() + min : tx[4] + delay,
689
680
  void 0,
690
- visible ? Promise.resolve(true) : void 0,
681
+ presentedPending ? Promise.resolve(true) : void 0,
691
682
  component
692
683
  ];
693
684
  }
@@ -697,14 +688,12 @@ function offerPending(router, tx) {
697
688
  clearTimeout(session[3]);
698
689
  const remaining = session[2] - Date.now();
699
690
  if (remaining > 0) {
700
- session[3] = setTimeout(() => {
701
- offerPending(router, tx);
702
- }, remaining);
691
+ session[3] = setTimeout(() => offerPending(router, tx), remaining);
703
692
  return;
704
693
  }
705
694
  session[2] = 0;
706
695
  }
707
- const offered = tx[3].map((match) => ({
696
+ const offered = matches.map((match) => ({
708
697
  ...match,
709
698
  _flight: void 0
710
699
  }));
@@ -1113,7 +1102,7 @@ async function loadClientRoute(router, opts) {
1113
1102
  if (match.isFetching) setFetching(router, match, false);
1114
1103
  }
1115
1104
  previousOwner[0].abort();
1116
- transferPredecessorResources(router, previousOwner[3], tx[3]);
1105
+ transferMatchResources(router, previousOwner[3], tx[3], true);
1117
1106
  }
1118
1107
  if (router._tx !== tx) {
1119
1108
  transferMatchResources(router, tx[3]);
@@ -1125,7 +1114,7 @@ async function loadClientRoute(router, opts) {
1125
1114
  router.stores.status.set("pending");
1126
1115
  router.stores.location.set(location);
1127
1116
  });
1128
- offerPending(router, tx);
1117
+ if (!resolvedLocation && !matches.some((match) => match._notFound)) offerPending(router, tx);
1129
1118
  try {
1130
1119
  await tx[5];
1131
1120
  } finally {