react-router 6.16.0 → 6.17.0-pre.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/CHANGELOG.md CHANGED
@@ -1,5 +1,62 @@
1
1
  # `react-router`
2
2
 
3
+ ## 6.17.0-pre.1
4
+
5
+ ### Patch Changes
6
+
7
+ - [REMOVE] fix lint issues ([#10930](https://github.com/remix-run/react-router/pull/10930))
8
+
9
+ ## 6.17.0-pre.0
10
+
11
+ ### Minor Changes
12
+
13
+ - Add support for the [View Transitions API](https://developer.mozilla.org/en-US/docs/Web/API/ViewTransition) via `document.startViewTransition` to enable CSS animated transitions on SPA navigations in your application. ([#10916](https://github.com/remix-run/react-router/pull/10916))
14
+
15
+ The simplest approach to enabling a View Transition in your React Router app is via the new `<Link unstable_viewTransition>` prop. This will cause the navigation DOM update to be wrapped in `document.startViewTransition` which will enable transitions for the DOM update. Without any additional CSS styles, you'll get a basic cross-fade animation for your page.
16
+
17
+ If you need to apply more fine-grained styles for your animations, you can leverage the `unstable_useViewTransitionState` hook which will tell you when a transition is in progress and you can use that to apply classes or styles:
18
+
19
+ ```jsx
20
+ function ImageLink(to, src, alt) {
21
+ let isTransitioning = unstable_useViewTransitionState(to);
22
+ return (
23
+ <Link to={to} unstable_viewTransition>
24
+ <img
25
+ src={src}
26
+ alt={alt}
27
+ style={{
28
+ viewTransitionName: isTransitioning ? "image-expand" : "",
29
+ }}
30
+ />
31
+ </Link>
32
+ );
33
+ }
34
+ ```
35
+
36
+ You can also use the `<NavLink unstable_viewTransition>` shorthand which will manage the hook usage for you and automatically add a `transitioning` class to the `<a>` during the transition:
37
+
38
+ ```css
39
+ a.transitioning img {
40
+ view-transition-name: "image-expand";
41
+ }
42
+ ```
43
+
44
+ ```jsx
45
+ <NavLink to={to} unstable_viewTransition>
46
+ <img src={src} alt={alt} />
47
+ </NavLink>
48
+ ```
49
+
50
+ For an example usage of View Transitions with React Router, check out [our fork](https://github.com/brophdawg11/react-router-records) of the [Astro Records](https://github.com/Charca/astro-records) demo.
51
+
52
+ For more information on using the View Transitions API, please refer to the [Smooth and simple transitions with the View Transitions API](https://developer.chrome.com/docs/web-platform/view-transitions/) guide from the Google Chrome team.
53
+
54
+ ### Patch Changes
55
+
56
+ - Fix `RouterProvider` `future` prop type to be a `Partial<FutureConfig>` so that not all flags must be specified ([#10900](https://github.com/remix-run/react-router/pull/10900))
57
+ - Updated dependencies:
58
+ - `@remix-run/router@1.10.0-pre.0`
59
+
3
60
  ## 6.16.0
4
61
 
5
62
  ### Minor Changes
package/dist/index.d.ts CHANGED
@@ -3,7 +3,7 @@ import { AbortedDeferredError, Action as NavigationType, createPath, defer, gene
3
3
  import type { AwaitProps, FutureConfig, IndexRouteProps, LayoutRouteProps, MemoryRouterProps, NavigateProps, OutletProps, PathRouteProps, RouteProps, RouterProps, RouterProviderProps, RoutesProps } from "./lib/components";
4
4
  import { Await, MemoryRouter, Navigate, Outlet, Route, Router, RouterProvider, Routes, createRoutesFromChildren, renderMatches } from "./lib/components";
5
5
  import type { DataRouteMatch, DataRouteObject, IndexRouteObject, NavigateOptions, Navigator, NonIndexRouteObject, RouteMatch, RouteObject } from "./lib/context";
6
- import { DataRouterContext, DataRouterStateContext, LocationContext, NavigationContext, RouteContext } from "./lib/context";
6
+ import { DataRouterContext, DataRouterStateContext, LocationContext, NavigationContext, RouteContext, ViewTransitionContext } from "./lib/context";
7
7
  import type { NavigateFunction } from "./lib/hooks";
8
8
  import { useActionData, useAsyncError, useAsyncValue, useBlocker, useHref, useInRouterContext, useLoaderData, useLocation, useMatch, useMatches, useNavigate, useNavigation, useNavigationType, useOutlet, useOutletContext, useParams, useResolvedPath, useRevalidator, useRouteError, useRouteId, useRouteLoaderData, useRoutes, useRoutesImpl } from "./lib/hooks";
9
9
  type Hash = string;
@@ -22,4 +22,4 @@ export declare function createMemoryRouter(routes: RouteObject[], opts?: {
22
22
  initialIndex?: number;
23
23
  }): RemixRouter;
24
24
  /** @internal */
25
- export { DataRouterContext as UNSAFE_DataRouterContext, DataRouterStateContext as UNSAFE_DataRouterStateContext, LocationContext as UNSAFE_LocationContext, NavigationContext as UNSAFE_NavigationContext, RouteContext as UNSAFE_RouteContext, mapRouteProperties as UNSAFE_mapRouteProperties, useRouteId as UNSAFE_useRouteId, useRoutesImpl as UNSAFE_useRoutesImpl, };
25
+ export { DataRouterContext as UNSAFE_DataRouterContext, DataRouterStateContext as UNSAFE_DataRouterStateContext, LocationContext as UNSAFE_LocationContext, NavigationContext as UNSAFE_NavigationContext, RouteContext as UNSAFE_RouteContext, ViewTransitionContext as UNSAFE_ViewTransitionContext, mapRouteProperties as UNSAFE_mapRouteProperties, useRouteId as UNSAFE_useRouteId, useRoutesImpl as UNSAFE_useRoutesImpl, };
package/dist/index.js CHANGED
@@ -1,5 +1,5 @@
1
1
  /**
2
- * React Router v6.16.0
2
+ * React Router v6.17.0-pre.1
3
3
  *
4
4
  * Copyright (c) Remix Software Inc.
5
5
  *
@@ -37,6 +37,12 @@ const DataRouterStateContext = /*#__PURE__*/React.createContext(null);
37
37
  if (process.env.NODE_ENV !== "production") {
38
38
  DataRouterStateContext.displayName = "DataRouterState";
39
39
  }
40
+ const ViewTransitionContext = /*#__PURE__*/React.createContext({
41
+ isTransitioning: false
42
+ });
43
+ if (process.env.NODE_ENV !== "production") {
44
+ ViewTransitionContext.displayName = "ViewTransition";
45
+ }
40
46
  const AwaitContext = /*#__PURE__*/React.createContext(null);
41
47
  if (process.env.NODE_ENV !== "production") {
42
48
  AwaitContext.displayName = "Await";
@@ -46,7 +52,7 @@ if (process.env.NODE_ENV !== "production") {
46
52
  * A Navigator is a "location changer"; it's how you get to different locations.
47
53
  *
48
54
  * Every history instance conforms to the Navigator interface, but the
49
- * distinction is useful primarily when it comes to the low-level <Router> API
55
+ * distinction is useful primarily when it comes to the low-level `<Router>` API
50
56
  * where both the location and a navigator must be provided separately in order
51
57
  * to avoid "tearing" that may occur in a suspense-enabled app if the action
52
58
  * and/or location were to be read directly from the history instance.
@@ -114,7 +120,7 @@ function useHref(to, _temp) {
114
120
  }
115
121
 
116
122
  /**
117
- * Returns true if this component is a descendant of a <Router>.
123
+ * Returns true if this component is a descendant of a `<Router>`.
118
124
  *
119
125
  * @see https://reactrouter.com/hooks/use-in-router-context
120
126
  */
@@ -152,7 +158,7 @@ function useNavigationType() {
152
158
  /**
153
159
  * Returns a PathMatch object if the given pattern matches the current URL.
154
160
  * This is useful for components that need to know "active" state, e.g.
155
- * <NavLink>.
161
+ * `<NavLink>`.
156
162
  *
157
163
  * @see https://reactrouter.com/hooks/use-match
158
164
  */
@@ -184,7 +190,7 @@ function useIsomorphicLayoutEffect(cb) {
184
190
  }
185
191
 
186
192
  /**
187
- * Returns an imperative method for changing the location. Used by <Link>s, but
193
+ * Returns an imperative method for changing the location. Used by `<Link>`s, but
188
194
  * may also be used by other elements to change the location.
189
195
  *
190
196
  * @see https://reactrouter.com/hooks/use-navigate
@@ -258,7 +264,7 @@ function useOutletContext() {
258
264
 
259
265
  /**
260
266
  * Returns the element for the child route at this level of the route
261
- * hierarchy. Used internally by <Outlet> to render child routes.
267
+ * hierarchy. Used internally by `<Outlet>` to render child routes.
262
268
  *
263
269
  * @see https://reactrouter.com/hooks/use-outlet
264
270
  */
@@ -308,7 +314,7 @@ function useResolvedPath(to, _temp2) {
308
314
  /**
309
315
  * Returns the element of the route that matched the current location, prepared
310
316
  * with the correct context to render the remainder of the route tree. Route
311
- * elements in the tree must render an <Outlet> to render their child route's
317
+ * elements in the tree must render an `<Outlet>` to render their child route's
312
318
  * element.
313
319
  *
314
320
  * @see https://reactrouter.com/hooks/use-routes
@@ -725,7 +731,7 @@ function useRouteError() {
725
731
  }
726
732
 
727
733
  /**
728
- * Returns the happy-path data from the nearest ancestor <Await /> value
734
+ * Returns the happy-path data from the nearest ancestor `<Await />` value
729
735
  */
730
736
  function useAsyncValue() {
731
737
  let value = React.useContext(AwaitContext);
@@ -733,7 +739,7 @@ function useAsyncValue() {
733
739
  }
734
740
 
735
741
  /**
736
- * Returns the error from the nearest ancestor <Await /> value
742
+ * Returns the error from the nearest ancestor `<Await />` value
737
743
  */
738
744
  function useAsyncError() {
739
745
  let value = React.useContext(AwaitContext);
@@ -867,6 +873,36 @@ function warningOnce(key, cond, message) {
867
873
  */
868
874
  const START_TRANSITION = "startTransition";
869
875
  const startTransitionImpl = React[START_TRANSITION];
876
+ function startTransitionSafe(cb) {
877
+ if (startTransitionImpl) {
878
+ startTransitionImpl(cb);
879
+ } else {
880
+ cb();
881
+ }
882
+ }
883
+ class Deferred {
884
+ // @ts-expect-error - no initializer
885
+
886
+ // @ts-expect-error - no initializer
887
+
888
+ constructor() {
889
+ this.status = "pending";
890
+ this.promise = new Promise((resolve, reject) => {
891
+ this.resolve = value => {
892
+ if (this.status === "pending") {
893
+ this.status = "resolved";
894
+ resolve(value);
895
+ }
896
+ };
897
+ this.reject = reason => {
898
+ if (this.status === "pending") {
899
+ this.status = "rejected";
900
+ reject(reason);
901
+ }
902
+ };
903
+ });
904
+ }
905
+ }
870
906
 
871
907
  /**
872
908
  * Given a Remix Router instance, render the appropriate UI
@@ -877,16 +913,108 @@ function RouterProvider(_ref) {
877
913
  router,
878
914
  future
879
915
  } = _ref;
880
- // Need to use a layout effect here so we are subscribed early enough to
881
- // pick up on any render-driven redirects/navigations (useEffect/<Navigate>)
882
916
  let [state, setStateImpl] = React.useState(router.state);
917
+ let [pendingState, setPendingState] = React.useState();
918
+ let [vtContext, setVtContext] = React.useState({
919
+ isTransitioning: false
920
+ });
921
+ let [renderDfd, setRenderDfd] = React.useState();
922
+ let [transition, setTransition] = React.useState();
923
+ let [interruption, setInterruption] = React.useState();
883
924
  let {
884
925
  v7_startTransition
885
926
  } = future || {};
886
- let setState = React.useCallback(newState => {
887
- v7_startTransition && startTransitionImpl ? startTransitionImpl(() => setStateImpl(newState)) : setStateImpl(newState);
888
- }, [setStateImpl, v7_startTransition]);
927
+ let optInStartTransition = React.useCallback(cb => {
928
+ if (v7_startTransition) {
929
+ startTransitionSafe(cb);
930
+ } else {
931
+ cb();
932
+ }
933
+ }, [v7_startTransition]);
934
+ let setState = React.useCallback((newState, _ref2) => {
935
+ let {
936
+ unstable_viewTransitionOpts: viewTransitionOpts
937
+ } = _ref2;
938
+ if (!viewTransitionOpts || router.window == null || typeof router.window.document.startViewTransition !== "function") {
939
+ // Mid-navigation state update, or startViewTransition isn't available
940
+ optInStartTransition(() => setStateImpl(newState));
941
+ } else if (transition && renderDfd) {
942
+ // Interrupting an in-progress transition, cancel and let everything flush
943
+ // out, and then kick off a new transition from the interruption state
944
+ renderDfd.resolve();
945
+ transition.skipTransition();
946
+ setInterruption({
947
+ state: newState,
948
+ currentLocation: viewTransitionOpts.currentLocation,
949
+ nextLocation: viewTransitionOpts.nextLocation
950
+ });
951
+ } else {
952
+ // Completed navigation update with opted-in view transitions, let 'er rip
953
+ setPendingState(newState);
954
+ setVtContext({
955
+ isTransitioning: true,
956
+ currentLocation: viewTransitionOpts.currentLocation,
957
+ nextLocation: viewTransitionOpts.nextLocation
958
+ });
959
+ }
960
+ }, [optInStartTransition, transition, renderDfd, router.window]);
961
+
962
+ // Need to use a layout effect here so we are subscribed early enough to
963
+ // pick up on any render-driven redirects/navigations (useEffect/<Navigate>)
889
964
  React.useLayoutEffect(() => router.subscribe(setState), [router, setState]);
965
+
966
+ // When we start a view transition, create a Deferred we can use for the
967
+ // eventual "completed" render
968
+ React.useEffect(() => {
969
+ if (vtContext.isTransitioning) {
970
+ setRenderDfd(new Deferred());
971
+ }
972
+ }, [vtContext.isTransitioning]);
973
+
974
+ // Once the deferred is created, kick off startViewTransition() to update the
975
+ // DOM and then wait on the Deferred to resolve (indicating the DOM update has
976
+ // happened)
977
+ React.useEffect(() => {
978
+ if (renderDfd && pendingState && router.window) {
979
+ let newState = pendingState;
980
+ let renderPromise = renderDfd.promise;
981
+ let transition = router.window.document.startViewTransition(async () => {
982
+ optInStartTransition(() => setStateImpl(newState));
983
+ await renderPromise;
984
+ });
985
+ transition.finished.finally(() => {
986
+ setRenderDfd(undefined);
987
+ setTransition(undefined);
988
+ setPendingState(undefined);
989
+ setVtContext({
990
+ isTransitioning: false
991
+ });
992
+ });
993
+ setTransition(transition);
994
+ }
995
+ }, [optInStartTransition, pendingState, renderDfd, router.window]);
996
+
997
+ // When the new location finally renders and is committed to the DOM, this
998
+ // effect will run to resolve the transition
999
+ React.useEffect(() => {
1000
+ if (renderDfd && pendingState && state.location.key === pendingState.location.key) {
1001
+ renderDfd.resolve();
1002
+ }
1003
+ }, [renderDfd, transition, state.location, pendingState]);
1004
+
1005
+ // If we get interrupted with a new navigation during a transition, we skip
1006
+ // the active transition, let it cleanup, then kick it off again here
1007
+ React.useEffect(() => {
1008
+ if (!vtContext.isTransitioning && interruption) {
1009
+ setPendingState(interruption.state);
1010
+ setVtContext({
1011
+ isTransitioning: true,
1012
+ currentLocation: interruption.currentLocation,
1013
+ nextLocation: interruption.nextLocation
1014
+ });
1015
+ setInterruption(undefined);
1016
+ }
1017
+ }, [vtContext.isTransitioning, interruption]);
890
1018
  let navigator = React.useMemo(() => {
891
1019
  return {
892
1020
  createHref: router.createHref,
@@ -921,6 +1049,8 @@ function RouterProvider(_ref) {
921
1049
  value: dataRouterContext
922
1050
  }, /*#__PURE__*/React.createElement(DataRouterStateContext.Provider, {
923
1051
  value: state
1052
+ }, /*#__PURE__*/React.createElement(ViewTransitionContext.Provider, {
1053
+ value: vtContext
924
1054
  }, /*#__PURE__*/React.createElement(Router, {
925
1055
  basename: basename,
926
1056
  location: state.location,
@@ -929,28 +1059,28 @@ function RouterProvider(_ref) {
929
1059
  }, state.initialized ? /*#__PURE__*/React.createElement(DataRoutes, {
930
1060
  routes: router.routes,
931
1061
  state: state
932
- }) : fallbackElement))), null);
1062
+ }) : fallbackElement)))), null);
933
1063
  }
934
- function DataRoutes(_ref2) {
1064
+ function DataRoutes(_ref3) {
935
1065
  let {
936
1066
  routes,
937
1067
  state
938
- } = _ref2;
1068
+ } = _ref3;
939
1069
  return useRoutesImpl(routes, undefined, state);
940
1070
  }
941
1071
  /**
942
- * A <Router> that stores all entries in memory.
1072
+ * A `<Router>` that stores all entries in memory.
943
1073
  *
944
1074
  * @see https://reactrouter.com/router-components/memory-router
945
1075
  */
946
- function MemoryRouter(_ref3) {
1076
+ function MemoryRouter(_ref4) {
947
1077
  let {
948
1078
  basename,
949
1079
  children,
950
1080
  initialEntries,
951
1081
  initialIndex,
952
1082
  future
953
- } = _ref3;
1083
+ } = _ref4;
954
1084
  let historyRef = React.useRef();
955
1085
  if (historyRef.current == null) {
956
1086
  historyRef.current = createMemoryHistory({
@@ -988,13 +1118,13 @@ function MemoryRouter(_ref3) {
988
1118
  *
989
1119
  * @see https://reactrouter.com/components/navigate
990
1120
  */
991
- function Navigate(_ref4) {
1121
+ function Navigate(_ref5) {
992
1122
  let {
993
1123
  to,
994
1124
  replace,
995
1125
  state,
996
1126
  relative
997
- } = _ref4;
1127
+ } = _ref5;
998
1128
  !useInRouterContext() ? process.env.NODE_ENV !== "production" ? UNSAFE_invariant(false, // TODO: This error is probably because they somehow have 2 versions of
999
1129
  // the router loaded. We can help them understand how to avoid that.
1000
1130
  "<Navigate> may be used only in the context of a <Router> component.") : UNSAFE_invariant(false) : void 0;
@@ -1037,13 +1167,13 @@ function Route(_props) {
1037
1167
  /**
1038
1168
  * Provides location context for the rest of the app.
1039
1169
  *
1040
- * Note: You usually won't render a <Router> directly. Instead, you'll render a
1041
- * router that is more specific to your environment such as a <BrowserRouter>
1042
- * in web browsers or a <StaticRouter> for server rendering.
1170
+ * Note: You usually won't render a `<Router>` directly. Instead, you'll render a
1171
+ * router that is more specific to your environment such as a `<BrowserRouter>`
1172
+ * in web browsers or a `<StaticRouter>` for server rendering.
1043
1173
  *
1044
1174
  * @see https://reactrouter.com/router-components/router
1045
1175
  */
1046
- function Router(_ref5) {
1176
+ function Router(_ref6) {
1047
1177
  let {
1048
1178
  basename: basenameProp = "/",
1049
1179
  children = null,
@@ -1051,7 +1181,7 @@ function Router(_ref5) {
1051
1181
  navigationType = Action.Pop,
1052
1182
  navigator,
1053
1183
  static: staticProp = false
1054
- } = _ref5;
1184
+ } = _ref6;
1055
1185
  !!useInRouterContext() ? process.env.NODE_ENV !== "production" ? UNSAFE_invariant(false, "You cannot render a <Router> inside another <Router>." + " You should never have more than one in your app.") : UNSAFE_invariant(false) : void 0;
1056
1186
 
1057
1187
  // Preserve trailing slashes on basename, so we can let the user control
@@ -1100,28 +1230,28 @@ function Router(_ref5) {
1100
1230
  }));
1101
1231
  }
1102
1232
  /**
1103
- * A container for a nested tree of <Route> elements that renders the branch
1233
+ * A container for a nested tree of `<Route>` elements that renders the branch
1104
1234
  * that best matches the current location.
1105
1235
  *
1106
1236
  * @see https://reactrouter.com/components/routes
1107
1237
  */
1108
- function Routes(_ref6) {
1238
+ function Routes(_ref7) {
1109
1239
  let {
1110
1240
  children,
1111
1241
  location
1112
- } = _ref6;
1242
+ } = _ref7;
1113
1243
  return useRoutes(createRoutesFromChildren(children), location);
1114
1244
  }
1115
1245
  /**
1116
1246
  * Component to use for rendering lazily loaded data from returning defer()
1117
1247
  * in a loader function
1118
1248
  */
1119
- function Await(_ref7) {
1249
+ function Await(_ref8) {
1120
1250
  let {
1121
1251
  children,
1122
1252
  errorElement,
1123
1253
  resolve
1124
- } = _ref7;
1254
+ } = _ref8;
1125
1255
  return /*#__PURE__*/React.createElement(AwaitErrorBoundary, {
1126
1256
  resolve: resolve,
1127
1257
  errorElement: errorElement
@@ -1224,12 +1354,12 @@ class AwaitErrorBoundary extends React.Component {
1224
1354
 
1225
1355
  /**
1226
1356
  * @private
1227
- * Indirection to leverage useAsyncValue for a render-prop API on <Await>
1357
+ * Indirection to leverage useAsyncValue for a render-prop API on `<Await>`
1228
1358
  */
1229
- function ResolveAwait(_ref8) {
1359
+ function ResolveAwait(_ref9) {
1230
1360
  let {
1231
1361
  children
1232
- } = _ref8;
1362
+ } = _ref9;
1233
1363
  let data = useAsyncValue();
1234
1364
  let toRender = typeof children === "function" ? children(data) : children;
1235
1365
  return /*#__PURE__*/React.createElement(React.Fragment, null, toRender);
@@ -1342,5 +1472,5 @@ function createMemoryRouter(routes, opts) {
1342
1472
  }).initialize();
1343
1473
  }
1344
1474
 
1345
- export { Await, MemoryRouter, Navigate, Outlet, Route, Router, RouterProvider, Routes, DataRouterContext as UNSAFE_DataRouterContext, DataRouterStateContext as UNSAFE_DataRouterStateContext, LocationContext as UNSAFE_LocationContext, NavigationContext as UNSAFE_NavigationContext, RouteContext as UNSAFE_RouteContext, mapRouteProperties as UNSAFE_mapRouteProperties, useRouteId as UNSAFE_useRouteId, useRoutesImpl as UNSAFE_useRoutesImpl, createMemoryRouter, createRoutesFromChildren, createRoutesFromChildren as createRoutesFromElements, renderMatches, useBlocker as unstable_useBlocker, useActionData, useAsyncError, useAsyncValue, useHref, useInRouterContext, useLoaderData, useLocation, useMatch, useMatches, useNavigate, useNavigation, useNavigationType, useOutlet, useOutletContext, useParams, useResolvedPath, useRevalidator, useRouteError, useRouteLoaderData, useRoutes };
1475
+ export { Await, MemoryRouter, Navigate, Outlet, Route, Router, RouterProvider, Routes, DataRouterContext as UNSAFE_DataRouterContext, DataRouterStateContext as UNSAFE_DataRouterStateContext, LocationContext as UNSAFE_LocationContext, NavigationContext as UNSAFE_NavigationContext, RouteContext as UNSAFE_RouteContext, ViewTransitionContext as UNSAFE_ViewTransitionContext, mapRouteProperties as UNSAFE_mapRouteProperties, useRouteId as UNSAFE_useRouteId, useRoutesImpl as UNSAFE_useRoutesImpl, createMemoryRouter, createRoutesFromChildren, createRoutesFromChildren as createRoutesFromElements, renderMatches, useBlocker as unstable_useBlocker, useActionData, useAsyncError, useAsyncValue, useHref, useInRouterContext, useLoaderData, useLocation, useMatch, useMatches, useNavigate, useNavigation, useNavigationType, useOutlet, useOutletContext, useParams, useResolvedPath, useRevalidator, useRouteError, useRouteLoaderData, useRoutes };
1346
1476
  //# sourceMappingURL=index.js.map