@tanstack/react-router 0.0.1-beta.193 → 0.0.1-beta.195

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.
@@ -238,13 +238,13 @@
238
238
  push: (path, state) => {
239
239
  assignKey(state);
240
240
  queueTask(() => {
241
- opts.pushState(path, state);
241
+ opts.pushState(path, state, onUpdate);
242
242
  });
243
243
  },
244
244
  replace: (path, state) => {
245
245
  assignKey(state);
246
246
  queueTask(() => {
247
- opts.replaceState(path, state);
247
+ opts.replaceState(path, state, onUpdate);
248
248
  });
249
249
  },
250
250
  go: index => {
@@ -276,7 +276,8 @@
276
276
  stopBlocking();
277
277
  }
278
278
  };
279
- }
279
+ },
280
+ flush: () => opts.flush?.()
280
281
  };
281
282
  }
282
283
  function assignKey(state) {
@@ -289,25 +290,100 @@
289
290
  // }
290
291
  }
291
292
 
293
+ /**
294
+ * Creates a history object that can be used to interact with the browser's
295
+ * navigation. This is a lightweight API wrapping the browser's native methods.
296
+ * It is designed to work with TanStack Router, but could be used as a standalone API as well.
297
+ * IMPORTANT: This API implements history throttling via a microtask to prevent
298
+ * excessive calls to the history API. In some browsers, calling history.pushState or
299
+ * history.replaceState in quick succession can cause the browser to ignore subsequent
300
+ * calls. This API smooths out those differences and ensures that your application
301
+ * state will *eventually* match the browser state. In most cases, this is not a problem,
302
+ * but if you need to ensure that the browser state is up to date, you can use the
303
+ * `history.flush` method to immediately flush all pending state changes to the browser URL.
304
+ * @param opts
305
+ * @param opts.getHref A function that returns the current href (path + search + hash)
306
+ * @param opts.createHref A function that takes a path and returns a href (path + search + hash)
307
+ * @returns A history instance
308
+ */
292
309
  function createBrowserHistory(opts) {
293
310
  const getHref = opts?.getHref ?? (() => `${window.location.pathname}${window.location.search}${window.location.hash}`);
294
311
  const createHref = opts?.createHref ?? (path => path);
295
- const getLocation = () => parseLocation(getHref(), window.history.state);
312
+ let currentLocation = parseLocation(getHref(), window.history.state);
313
+ const getLocation = () => currentLocation;
314
+ let next;
315
+
316
+ // Because we are proactively updating the location
317
+ // in memory before actually updating the browser history,
318
+ // we need to track when we are doing this so we don't
319
+ // notify subscribers twice on the last update.
320
+ let tracking = true;
321
+
322
+ // We need to track the current scheduled update to prevent
323
+ // multiple updates from being scheduled at the same time.
324
+ let scheduled;
325
+
326
+ // This function is a wrapper to prevent any of the callback's
327
+ // side effects from causing a subscriber notification
328
+ const untrack = fn => {
329
+ tracking = false;
330
+ fn();
331
+ tracking = true;
332
+ };
333
+
334
+ // This function flushes the next update to the browser history
335
+ const flush = () => {
336
+ // Do not notify subscribers about this push/replace call
337
+ untrack(() => {
338
+ if (!next) return;
339
+ window.history[next.isPush ? 'pushState' : 'replaceState'](next.state, '', next.href);
340
+ // Reset the nextIsPush flag and clear the scheduled update
341
+ next = undefined;
342
+ scheduled = undefined;
343
+ });
344
+ };
345
+
346
+ // This function queues up a call to update the browser history
347
+ const queueHistoryAction = (type, path, state, onUpdate) => {
348
+ const href = createHref(path);
349
+
350
+ // Update the location in memory
351
+ currentLocation = parseLocation(href, state);
352
+
353
+ // Keep track of the next location we need to flush to the URL
354
+ next = {
355
+ href,
356
+ state,
357
+ isPush: next?.isPush || type === 'push'
358
+ };
359
+ // Notify subscribers
360
+ onUpdate();
361
+ if (!scheduled) {
362
+ // Schedule an update to the browser history
363
+ scheduled = Promise.resolve().then(() => flush());
364
+ }
365
+ };
296
366
  return createHistory({
297
367
  getLocation,
298
368
  subscriber: onUpdate => {
299
- window.addEventListener(pushStateEvent, onUpdate);
300
- window.addEventListener(popStateEvent, onUpdate);
369
+ window.addEventListener(pushStateEvent, () => {
370
+ currentLocation = parseLocation(getHref(), window.history.state);
371
+ onUpdate();
372
+ });
373
+ window.addEventListener(popStateEvent, () => {
374
+ currentLocation = parseLocation(getHref(), window.history.state);
375
+ onUpdate();
376
+ });
301
377
  var pushState = window.history.pushState;
302
378
  window.history.pushState = function () {
303
379
  let res = pushState.apply(history, arguments);
304
- onUpdate();
380
+ if (tracking) onUpdate();
305
381
  return res;
306
382
  };
307
383
  var replaceState = window.history.replaceState;
308
384
  window.history.replaceState = function () {
309
385
  let res = replaceState.apply(history, arguments);
310
- onUpdate();
386
+ if (tracking) onUpdate();
311
387
  return res;
312
388
  };
313
389
  return () => {
@@ -317,16 +393,13 @@
317
393
  window.removeEventListener(popStateEvent, onUpdate);
318
394
  };
319
395
  },
320
- pushState: (path, state) => {
321
- window.history.pushState(state, '', createHref(path));
322
- },
323
- replaceState: (path, state) => {
324
- window.history.replaceState(state, '', createHref(path));
325
- },
396
+ pushState: (path, state, onUpdate) => queueHistoryAction('push', path, state, onUpdate),
397
+ replaceState: (path, state, onUpdate) => queueHistoryAction('replace', path, state, onUpdate),
326
398
  back: () => window.history.back(),
327
399
  forward: () => window.history.forward(),
328
400
  go: n => window.history.go(n),
329
- createHref: path => createHref(path)
401
+ createHref: path => createHref(path),
402
+ flush
330
403
  });
331
404
  }
332
405
  function createHashHistory() {
@@ -383,6 +456,8 @@
383
456
  return (Math.random() + 1).toString(36).substring(7);
384
457
  }
385
458
 
459
+ // export type Expand<T> = T
460
+
386
461
  // type Compute<T> = { [K in keyof T]: T[K] } | never
387
462
 
388
463
  // type AllKeys<T> = T extends any ? keyof T : never
@@ -1409,7 +1484,7 @@
1409
1484
  };
1410
1485
  this.setRouteMatch(match.id, s => ({
1411
1486
  ...s,
1412
- context
1487
+ context: replaceEqualDeep(s.context, context)
1413
1488
  }));
1414
1489
  } catch (err) {
1415
1490
  handleError(err, 'BEFORE_LOAD');
@@ -2287,6 +2362,7 @@
2287
2362
  return _extends.apply(this, arguments);
2288
2363
  }
2289
2364
 
2365
+ const useLayoutEffect$1 = typeof window !== 'undefined' ? React__namespace.useLayoutEffect : React__namespace.useEffect;
2290
2366
  Route.__onInit = route => {
2291
2367
  Object.assign(route, {
2292
2368
  useMatch: (opts = {}) => {
@@ -2301,18 +2377,11 @@
2301
2377
  from: route.id
2302
2378
  });
2303
2379
  },
2304
- useContext: (opts = {}) => {
2305
- return useMatch({
2306
- ...opts,
2307
- from: route.id,
2308
- select: d => opts?.select ? opts.select(d.context) : d.context
2309
- });
2310
- },
2311
2380
  useRouteContext: (opts = {}) => {
2312
2381
  return useMatch({
2313
2382
  ...opts,
2314
2383
  from: route.id,
2315
- select: d => opts?.select ? opts.select(d.routeContext) : d.routeContext
2384
+ select: d => opts?.select ? opts.select(d.context) : d.context
2316
2385
  });
2317
2386
  },
2318
2387
  useSearch: (opts = {}) => {
@@ -2459,7 +2528,7 @@
2459
2528
  });
2460
2529
  function Navigate(props) {
2461
2530
  const router = useRouter();
2462
- React__namespace.useLayoutEffect(() => {
2531
+ useLayoutEffect$1(() => {
2463
2532
  router.navigate(props);
2464
2533
  }, []);
2465
2534
  return null;
@@ -2502,7 +2571,6 @@
2502
2571
  return /*#__PURE__*/React__namespace.createElement(ErrorComponent, {
2503
2572
  ...props,
2504
2573
  useMatch: route.useMatch,
2505
- useContext: route.useContext,
2506
2574
  useRouteContext: route.useRouteContext,
2507
2575
  useSearch: route.useSearch,
2508
2576
  useParams: route.useParams
@@ -2647,7 +2715,6 @@
2647
2715
  return /*#__PURE__*/React__namespace.createElement(routeErrorComponent, {
2648
2716
  ...props,
2649
2717
  useMatch: route.useMatch,
2650
- useContext: route.useContext,
2651
2718
  useRouteContext: route.useRouteContext,
2652
2719
  useSearch: route.useSearch,
2653
2720
  useParams: route.useParams
@@ -2658,7 +2725,6 @@
2658
2725
  }, /*#__PURE__*/React__namespace.createElement(ResolvedSuspenseBoundary, {
2659
2726
  fallback: /*#__PURE__*/React__namespace.createElement(PendingComponent, {
2660
2727
  useMatch: route.useMatch,
2661
- useContext: route.useContext,
2662
2728
  useRouteContext: route.useRouteContext,
2663
2729
  useSearch: route.useSearch,
2664
2730
  useParams: route.useParams
@@ -2693,7 +2759,6 @@
2693
2759
  return /*#__PURE__*/React__namespace.createElement(PendingComponent, {
2694
2760
  useLoader: route.useLoader,
2695
2761
  useMatch: route.useMatch,
2696
- useContext: route.useContext,
2697
2762
  useRouteContext: route.useRouteContext,
2698
2763
  useSearch: route.useSearch,
2699
2764
  useParams: route.useParams
@@ -2705,7 +2770,6 @@
2705
2770
  return /*#__PURE__*/React__namespace.createElement(comp, {
2706
2771
  useLoader: route.useLoader,
2707
2772
  useMatch: route.useMatch,
2708
- useContext: route.useContext,
2709
2773
  useRouteContext: route.useRouteContext,
2710
2774
  useSearch: route.useSearch,
2711
2775
  useParams: route.useParams