@tinkoff/router 0.3.1 → 0.3.8
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/lib/components/react/context.browser.js +4 -1
- package/lib/components/react/context.d.ts +8 -0
- package/lib/components/react/context.es.js +4 -1
- package/lib/components/react/context.js +4 -0
- package/lib/components/react/index.browser.js +12 -0
- package/lib/components/react/index.d.ts +3 -1
- package/lib/components/react/index.es.js +12 -0
- package/lib/components/react/index.js +16 -0
- package/lib/components/react/providers/default.browser.js +17 -0
- package/lib/components/react/providers/default.d.ts +4 -0
- package/lib/components/react/providers/default.es.js +17 -0
- package/lib/components/react/providers/default.js +21 -0
- package/lib/components/react/providers/transitions.browser.js +89 -0
- package/lib/components/react/providers/transitions.d.ts +5 -0
- package/lib/components/react/providers/transitions.es.js +89 -0
- package/lib/components/react/providers/transitions.js +94 -0
- package/lib/components/react/providers/types.d.ts +16 -0
- package/lib/components/react/useViewTransition.browser.js +22 -0
- package/lib/components/react/useViewTransition.d.ts +5 -0
- package/lib/components/react/useViewTransition.es.js +22 -0
- package/lib/components/react/useViewTransition.js +26 -0
- package/lib/index.browser.js +4 -3
- package/lib/index.es.js +4 -3
- package/lib/index.js +10 -8
- package/lib/router/abstract.browser.js +8 -3
- package/lib/router/abstract.d.ts +3 -1
- package/lib/router/abstract.es.js +8 -3
- package/lib/router/abstract.js +8 -3
- package/lib/router/browser.browser.js +66 -1
- package/lib/router/browser.d.ts +5 -1
- package/lib/types.d.ts +5 -0
- package/package.json +2 -1
- package/lib/components/react/provider.browser.js +0 -16
- package/lib/components/react/provider.d.ts +0 -13
- package/lib/components/react/provider.es.js +0 -16
- package/lib/components/react/provider.js +0 -20
|
@@ -3,5 +3,8 @@ import { createContext } from 'react';
|
|
|
3
3
|
const RouterContext = createContext(null);
|
|
4
4
|
const RouteContext = createContext(null);
|
|
5
5
|
const UrlContext = createContext(null);
|
|
6
|
+
const ViewTransitionContext = createContext({
|
|
7
|
+
isTransitioning: false,
|
|
8
|
+
});
|
|
6
9
|
|
|
7
|
-
export { RouteContext, RouterContext, UrlContext };
|
|
10
|
+
export { RouteContext, RouterContext, UrlContext, ViewTransitionContext };
|
|
@@ -4,4 +4,12 @@ import type { NavigationRoute } from '../../types';
|
|
|
4
4
|
export declare const RouterContext: import("react").Context<AbstractRouter>;
|
|
5
5
|
export declare const RouteContext: import("react").Context<NavigationRoute>;
|
|
6
6
|
export declare const UrlContext: import("react").Context<Url>;
|
|
7
|
+
export type ViewTransitionState = {
|
|
8
|
+
isTransitioning: false;
|
|
9
|
+
} | {
|
|
10
|
+
isTransitioning: true;
|
|
11
|
+
currentRoute: NavigationRoute;
|
|
12
|
+
nextRoute: NavigationRoute;
|
|
13
|
+
};
|
|
14
|
+
export declare const ViewTransitionContext: import("react").Context<ViewTransitionState>;
|
|
7
15
|
//# sourceMappingURL=context.d.ts.map
|
|
@@ -3,5 +3,8 @@ import { createContext } from 'react';
|
|
|
3
3
|
const RouterContext = createContext(null);
|
|
4
4
|
const RouteContext = createContext(null);
|
|
5
5
|
const UrlContext = createContext(null);
|
|
6
|
+
const ViewTransitionContext = createContext({
|
|
7
|
+
isTransitioning: false,
|
|
8
|
+
});
|
|
6
9
|
|
|
7
|
-
export { RouteContext, RouterContext, UrlContext };
|
|
10
|
+
export { RouteContext, RouterContext, UrlContext, ViewTransitionContext };
|
|
@@ -7,7 +7,11 @@ var react = require('react');
|
|
|
7
7
|
const RouterContext = react.createContext(null);
|
|
8
8
|
const RouteContext = react.createContext(null);
|
|
9
9
|
const UrlContext = react.createContext(null);
|
|
10
|
+
const ViewTransitionContext = react.createContext({
|
|
11
|
+
isTransitioning: false,
|
|
12
|
+
});
|
|
10
13
|
|
|
11
14
|
exports.RouteContext = RouteContext;
|
|
12
15
|
exports.RouterContext = RouterContext;
|
|
13
16
|
exports.UrlContext = UrlContext;
|
|
17
|
+
exports.ViewTransitionContext = ViewTransitionContext;
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { TransitionsProvider } from './providers/transitions.browser.js';
|
|
2
|
+
import { DefaultProvider } from './providers/default.browser.js';
|
|
3
|
+
import 'react';
|
|
4
|
+
import './context.browser.js';
|
|
5
|
+
import '@tinkoff/react-hooks';
|
|
6
|
+
|
|
7
|
+
const Provider = process.env.__TRAMVAI_VIEW_TRANSITIONS === 'true' ||
|
|
8
|
+
process.env.__TRAMVAI_REACT_TRANSITIONS === 'true'
|
|
9
|
+
? TransitionsProvider
|
|
10
|
+
: DefaultProvider;
|
|
11
|
+
|
|
12
|
+
export { Provider };
|
|
@@ -1,6 +1,8 @@
|
|
|
1
|
-
export {
|
|
1
|
+
export type { RouterState } from './providers/types';
|
|
2
2
|
export { useRouter } from './useRouter';
|
|
3
3
|
export { useRoute } from './useRoute';
|
|
4
4
|
export { useUrl } from './useUrl';
|
|
5
5
|
export { useNavigate } from './useNavigate';
|
|
6
|
+
export { useViewTransition } from './useViewTransition';
|
|
7
|
+
export declare const Provider: import("react").FunctionComponent<import("./providers/types").RouterProviderProps>;
|
|
6
8
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { TransitionsProvider } from './providers/transitions.es.js';
|
|
2
|
+
import { DefaultProvider } from './providers/default.es.js';
|
|
3
|
+
import 'react';
|
|
4
|
+
import './context.es.js';
|
|
5
|
+
import '@tinkoff/react-hooks';
|
|
6
|
+
|
|
7
|
+
const Provider = process.env.__TRAMVAI_VIEW_TRANSITIONS === 'true' ||
|
|
8
|
+
process.env.__TRAMVAI_REACT_TRANSITIONS === 'true'
|
|
9
|
+
? TransitionsProvider
|
|
10
|
+
: DefaultProvider;
|
|
11
|
+
|
|
12
|
+
export { Provider };
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
Object.defineProperty(exports, '__esModule', { value: true });
|
|
4
|
+
|
|
5
|
+
var transitions = require('./providers/transitions.js');
|
|
6
|
+
var _default = require('./providers/default.js');
|
|
7
|
+
require('react');
|
|
8
|
+
require('./context.js');
|
|
9
|
+
require('@tinkoff/react-hooks');
|
|
10
|
+
|
|
11
|
+
const Provider = process.env.__TRAMVAI_VIEW_TRANSITIONS === 'true' ||
|
|
12
|
+
process.env.__TRAMVAI_REACT_TRANSITIONS === 'true'
|
|
13
|
+
? transitions.TransitionsProvider
|
|
14
|
+
: _default.DefaultProvider;
|
|
15
|
+
|
|
16
|
+
exports.Provider = Provider;
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { jsx } from 'react/jsx-runtime';
|
|
2
|
+
import { useMemo, useCallback } from 'react';
|
|
3
|
+
import { useSyncExternalStore } from 'use-sync-external-store/shim';
|
|
4
|
+
import { RouterContext, RouteContext, UrlContext } from '../context.browser.js';
|
|
5
|
+
|
|
6
|
+
const DefaultProvider = ({ router, children, serverState, }) => {
|
|
7
|
+
// fallback to outdated router implementation
|
|
8
|
+
const lastRoute = useMemo(() => router.getCurrentRoute(), [router]);
|
|
9
|
+
const lastUrl = useMemo(() => router.getCurrentUrl(), [router]);
|
|
10
|
+
const subscription = useCallback((cb) => router.registerSyncHook('change', cb), [router]);
|
|
11
|
+
const route = useSyncExternalStore(subscription, () => { var _a, _b; return (_b = (_a = router.getLastRoute) === null || _a === void 0 ? void 0 : _a.call(router)) !== null && _b !== void 0 ? _b : lastRoute; }, serverState ? () => serverState.route : () => { var _a, _b; return (_b = (_a = router.getLastRoute) === null || _a === void 0 ? void 0 : _a.call(router)) !== null && _b !== void 0 ? _b : lastRoute; });
|
|
12
|
+
const url = useSyncExternalStore(subscription, () => { var _a, _b; return (_b = (_a = router.getLastUrl) === null || _a === void 0 ? void 0 : _a.call(router)) !== null && _b !== void 0 ? _b : lastUrl; }, serverState ? () => serverState.url : () => { var _a, _b; return (_b = (_a = router.getLastUrl) === null || _a === void 0 ? void 0 : _a.call(router)) !== null && _b !== void 0 ? _b : lastUrl; });
|
|
13
|
+
return (jsx(RouterContext.Provider, { value: router, children: jsx(RouteContext.Provider, { value: route, children: jsx(UrlContext.Provider, { value: url, children: children }) }) }));
|
|
14
|
+
};
|
|
15
|
+
DefaultProvider.displayName = 'TinkoffRouterProvider';
|
|
16
|
+
|
|
17
|
+
export { DefaultProvider };
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { jsx } from 'react/jsx-runtime';
|
|
2
|
+
import { useMemo, useCallback } from 'react';
|
|
3
|
+
import { useSyncExternalStore } from 'use-sync-external-store/shim';
|
|
4
|
+
import { RouterContext, RouteContext, UrlContext } from '../context.es.js';
|
|
5
|
+
|
|
6
|
+
const DefaultProvider = ({ router, children, serverState, }) => {
|
|
7
|
+
// fallback to outdated router implementation
|
|
8
|
+
const lastRoute = useMemo(() => router.getCurrentRoute(), [router]);
|
|
9
|
+
const lastUrl = useMemo(() => router.getCurrentUrl(), [router]);
|
|
10
|
+
const subscription = useCallback((cb) => router.registerSyncHook('change', cb), [router]);
|
|
11
|
+
const route = useSyncExternalStore(subscription, () => { var _a, _b; return (_b = (_a = router.getLastRoute) === null || _a === void 0 ? void 0 : _a.call(router)) !== null && _b !== void 0 ? _b : lastRoute; }, serverState ? () => serverState.route : () => { var _a, _b; return (_b = (_a = router.getLastRoute) === null || _a === void 0 ? void 0 : _a.call(router)) !== null && _b !== void 0 ? _b : lastRoute; });
|
|
12
|
+
const url = useSyncExternalStore(subscription, () => { var _a, _b; return (_b = (_a = router.getLastUrl) === null || _a === void 0 ? void 0 : _a.call(router)) !== null && _b !== void 0 ? _b : lastUrl; }, serverState ? () => serverState.url : () => { var _a, _b; return (_b = (_a = router.getLastUrl) === null || _a === void 0 ? void 0 : _a.call(router)) !== null && _b !== void 0 ? _b : lastUrl; });
|
|
13
|
+
return (jsx(RouterContext.Provider, { value: router, children: jsx(RouteContext.Provider, { value: route, children: jsx(UrlContext.Provider, { value: url, children: children }) }) }));
|
|
14
|
+
};
|
|
15
|
+
DefaultProvider.displayName = 'TinkoffRouterProvider';
|
|
16
|
+
|
|
17
|
+
export { DefaultProvider };
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
Object.defineProperty(exports, '__esModule', { value: true });
|
|
4
|
+
|
|
5
|
+
var jsxRuntime = require('react/jsx-runtime');
|
|
6
|
+
var react = require('react');
|
|
7
|
+
var shim = require('use-sync-external-store/shim');
|
|
8
|
+
var context = require('../context.js');
|
|
9
|
+
|
|
10
|
+
const DefaultProvider = ({ router, children, serverState, }) => {
|
|
11
|
+
// fallback to outdated router implementation
|
|
12
|
+
const lastRoute = react.useMemo(() => router.getCurrentRoute(), [router]);
|
|
13
|
+
const lastUrl = react.useMemo(() => router.getCurrentUrl(), [router]);
|
|
14
|
+
const subscription = react.useCallback((cb) => router.registerSyncHook('change', cb), [router]);
|
|
15
|
+
const route = shim.useSyncExternalStore(subscription, () => { var _a, _b; return (_b = (_a = router.getLastRoute) === null || _a === void 0 ? void 0 : _a.call(router)) !== null && _b !== void 0 ? _b : lastRoute; }, serverState ? () => serverState.route : () => { var _a, _b; return (_b = (_a = router.getLastRoute) === null || _a === void 0 ? void 0 : _a.call(router)) !== null && _b !== void 0 ? _b : lastRoute; });
|
|
16
|
+
const url = shim.useSyncExternalStore(subscription, () => { var _a, _b; return (_b = (_a = router.getLastUrl) === null || _a === void 0 ? void 0 : _a.call(router)) !== null && _b !== void 0 ? _b : lastUrl; }, serverState ? () => serverState.url : () => { var _a, _b; return (_b = (_a = router.getLastUrl) === null || _a === void 0 ? void 0 : _a.call(router)) !== null && _b !== void 0 ? _b : lastUrl; });
|
|
17
|
+
return (jsxRuntime.jsx(context.RouterContext.Provider, { value: router, children: jsxRuntime.jsx(context.RouteContext.Provider, { value: route, children: jsxRuntime.jsx(context.UrlContext.Provider, { value: url, children: children }) }) }));
|
|
18
|
+
};
|
|
19
|
+
DefaultProvider.displayName = 'TinkoffRouterProvider';
|
|
20
|
+
|
|
21
|
+
exports.DefaultProvider = DefaultProvider;
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import { jsx } from 'react/jsx-runtime';
|
|
2
|
+
import { useState, useEffect } from 'react';
|
|
3
|
+
import { useIsomorphicLayoutEffect } from '@tinkoff/react-hooks';
|
|
4
|
+
import { Deferred } from '@tramvai/core';
|
|
5
|
+
import { RouterContext, RouteContext, UrlContext, ViewTransitionContext } from '../context.browser.js';
|
|
6
|
+
|
|
7
|
+
let startTransition;
|
|
8
|
+
try {
|
|
9
|
+
// eslint-disable-next-line import/no-unresolved, import/extensions
|
|
10
|
+
startTransition = require('react').startTransition;
|
|
11
|
+
}
|
|
12
|
+
catch { }
|
|
13
|
+
const startReactTransition = typeof startTransition === 'function' && process.env.__TRAMVAI_REACT_TRANSITIONS === 'true'
|
|
14
|
+
? startTransition
|
|
15
|
+
: (fn) => {
|
|
16
|
+
fn();
|
|
17
|
+
};
|
|
18
|
+
const TransitionsProvider = ({ router, children, }) => {
|
|
19
|
+
// fallback to outdated router implementation
|
|
20
|
+
const [state, setState] = useState(() => {
|
|
21
|
+
var _a, _b;
|
|
22
|
+
return ({
|
|
23
|
+
route: (_a = router.getLastRoute()) !== null && _a !== void 0 ? _a : router.getCurrentRoute(),
|
|
24
|
+
url: (_b = router.getLastUrl()) !== null && _b !== void 0 ? _b : router.getCurrentUrl(),
|
|
25
|
+
});
|
|
26
|
+
});
|
|
27
|
+
// Router state that will be applied to the DOM.
|
|
28
|
+
const [pendingState, setPendingState] = useState(null);
|
|
29
|
+
// View transition state for context.
|
|
30
|
+
const [viewTransitionState, setViewTransitionState] = useState({
|
|
31
|
+
isTransitioning: false,
|
|
32
|
+
});
|
|
33
|
+
// Deferred render promise, that resolves when the DOM updates after a navigation.
|
|
34
|
+
const [renderDeferred, setRenderDeferred] = useState(null);
|
|
35
|
+
const updateRouterState = (navigation) => {
|
|
36
|
+
if (document.startViewTransition !== undefined && navigation.viewTransition) {
|
|
37
|
+
setPendingState({ route: navigation.to, url: navigation.url });
|
|
38
|
+
setViewTransitionState({
|
|
39
|
+
isTransitioning: true,
|
|
40
|
+
currentRoute: navigation.from,
|
|
41
|
+
nextRoute: navigation.to,
|
|
42
|
+
});
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
45
|
+
startReactTransition(() => {
|
|
46
|
+
setState({ route: navigation.to, url: navigation.url });
|
|
47
|
+
});
|
|
48
|
+
};
|
|
49
|
+
useIsomorphicLayoutEffect(() => router.registerSyncHook('change', updateRouterState), [router]);
|
|
50
|
+
// It's okay to ignore rules of hooks here, because this code
|
|
51
|
+
// will not be a part of the bundle in case of truly condition
|
|
52
|
+
if (process.env.__TRAMVAI_VIEW_TRANSITIONS === 'true') {
|
|
53
|
+
// eslint-disable-next-line react-hooks/rules-of-hooks
|
|
54
|
+
useEffect(() => {
|
|
55
|
+
if (viewTransitionState.isTransitioning) {
|
|
56
|
+
setRenderDeferred(new Deferred());
|
|
57
|
+
}
|
|
58
|
+
}, [viewTransitionState.isTransitioning]);
|
|
59
|
+
// eslint-disable-next-line react-hooks/rules-of-hooks
|
|
60
|
+
useEffect(() => {
|
|
61
|
+
if (renderDeferred !== null && pendingState !== null) {
|
|
62
|
+
const transition = document.startViewTransition(async () => {
|
|
63
|
+
startReactTransition(() => {
|
|
64
|
+
setState(pendingState);
|
|
65
|
+
});
|
|
66
|
+
await renderDeferred.promise;
|
|
67
|
+
});
|
|
68
|
+
// eslint-disable-next-line promise/catch-or-return
|
|
69
|
+
transition.finished.finally(() => {
|
|
70
|
+
setRenderDeferred(null);
|
|
71
|
+
setPendingState(null);
|
|
72
|
+
setViewTransitionState({ isTransitioning: false });
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
}, [pendingState, renderDeferred]);
|
|
76
|
+
// eslint-disable-next-line react-hooks/rules-of-hooks
|
|
77
|
+
useEffect(() => {
|
|
78
|
+
if (renderDeferred !== null &&
|
|
79
|
+
pendingState !== null &&
|
|
80
|
+
state.route.actualPath === pendingState.route.actualPath) {
|
|
81
|
+
renderDeferred.resolve();
|
|
82
|
+
}
|
|
83
|
+
}, [state, renderDeferred, pendingState]);
|
|
84
|
+
}
|
|
85
|
+
return (jsx(RouterContext.Provider, { value: router, children: jsx(RouteContext.Provider, { value: state.route, children: jsx(UrlContext.Provider, { value: state.url, children: jsx(ViewTransitionContext.Provider, { value: viewTransitionState, children: children }) }) }) }));
|
|
86
|
+
};
|
|
87
|
+
TransitionsProvider.displayName = 'TinkoffRouterTransitionsProvider';
|
|
88
|
+
|
|
89
|
+
export { TransitionsProvider, startReactTransition };
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import type { FunctionComponent } from 'react';
|
|
2
|
+
import type { RouterProviderProps } from './types';
|
|
3
|
+
export declare const startReactTransition: any;
|
|
4
|
+
export declare const TransitionsProvider: FunctionComponent<RouterProviderProps>;
|
|
5
|
+
//# sourceMappingURL=transitions.d.ts.map
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import { jsx } from 'react/jsx-runtime';
|
|
2
|
+
import { useState, useEffect } from 'react';
|
|
3
|
+
import { useIsomorphicLayoutEffect } from '@tinkoff/react-hooks';
|
|
4
|
+
import { Deferred } from '@tramvai/core';
|
|
5
|
+
import { RouterContext, RouteContext, UrlContext, ViewTransitionContext } from '../context.es.js';
|
|
6
|
+
|
|
7
|
+
let startTransition;
|
|
8
|
+
try {
|
|
9
|
+
// eslint-disable-next-line import/no-unresolved, import/extensions
|
|
10
|
+
startTransition = require('react').startTransition;
|
|
11
|
+
}
|
|
12
|
+
catch { }
|
|
13
|
+
const startReactTransition = typeof startTransition === 'function' && process.env.__TRAMVAI_REACT_TRANSITIONS === 'true'
|
|
14
|
+
? startTransition
|
|
15
|
+
: (fn) => {
|
|
16
|
+
fn();
|
|
17
|
+
};
|
|
18
|
+
const TransitionsProvider = ({ router, children, }) => {
|
|
19
|
+
// fallback to outdated router implementation
|
|
20
|
+
const [state, setState] = useState(() => {
|
|
21
|
+
var _a, _b;
|
|
22
|
+
return ({
|
|
23
|
+
route: (_a = router.getLastRoute()) !== null && _a !== void 0 ? _a : router.getCurrentRoute(),
|
|
24
|
+
url: (_b = router.getLastUrl()) !== null && _b !== void 0 ? _b : router.getCurrentUrl(),
|
|
25
|
+
});
|
|
26
|
+
});
|
|
27
|
+
// Router state that will be applied to the DOM.
|
|
28
|
+
const [pendingState, setPendingState] = useState(null);
|
|
29
|
+
// View transition state for context.
|
|
30
|
+
const [viewTransitionState, setViewTransitionState] = useState({
|
|
31
|
+
isTransitioning: false,
|
|
32
|
+
});
|
|
33
|
+
// Deferred render promise, that resolves when the DOM updates after a navigation.
|
|
34
|
+
const [renderDeferred, setRenderDeferred] = useState(null);
|
|
35
|
+
const updateRouterState = (navigation) => {
|
|
36
|
+
if (document.startViewTransition !== undefined && navigation.viewTransition) {
|
|
37
|
+
setPendingState({ route: navigation.to, url: navigation.url });
|
|
38
|
+
setViewTransitionState({
|
|
39
|
+
isTransitioning: true,
|
|
40
|
+
currentRoute: navigation.from,
|
|
41
|
+
nextRoute: navigation.to,
|
|
42
|
+
});
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
45
|
+
startReactTransition(() => {
|
|
46
|
+
setState({ route: navigation.to, url: navigation.url });
|
|
47
|
+
});
|
|
48
|
+
};
|
|
49
|
+
useIsomorphicLayoutEffect(() => router.registerSyncHook('change', updateRouterState), [router]);
|
|
50
|
+
// It's okay to ignore rules of hooks here, because this code
|
|
51
|
+
// will not be a part of the bundle in case of truly condition
|
|
52
|
+
if (process.env.__TRAMVAI_VIEW_TRANSITIONS === 'true') {
|
|
53
|
+
// eslint-disable-next-line react-hooks/rules-of-hooks
|
|
54
|
+
useEffect(() => {
|
|
55
|
+
if (viewTransitionState.isTransitioning) {
|
|
56
|
+
setRenderDeferred(new Deferred());
|
|
57
|
+
}
|
|
58
|
+
}, [viewTransitionState.isTransitioning]);
|
|
59
|
+
// eslint-disable-next-line react-hooks/rules-of-hooks
|
|
60
|
+
useEffect(() => {
|
|
61
|
+
if (renderDeferred !== null && pendingState !== null) {
|
|
62
|
+
const transition = document.startViewTransition(async () => {
|
|
63
|
+
startReactTransition(() => {
|
|
64
|
+
setState(pendingState);
|
|
65
|
+
});
|
|
66
|
+
await renderDeferred.promise;
|
|
67
|
+
});
|
|
68
|
+
// eslint-disable-next-line promise/catch-or-return
|
|
69
|
+
transition.finished.finally(() => {
|
|
70
|
+
setRenderDeferred(null);
|
|
71
|
+
setPendingState(null);
|
|
72
|
+
setViewTransitionState({ isTransitioning: false });
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
}, [pendingState, renderDeferred]);
|
|
76
|
+
// eslint-disable-next-line react-hooks/rules-of-hooks
|
|
77
|
+
useEffect(() => {
|
|
78
|
+
if (renderDeferred !== null &&
|
|
79
|
+
pendingState !== null &&
|
|
80
|
+
state.route.actualPath === pendingState.route.actualPath) {
|
|
81
|
+
renderDeferred.resolve();
|
|
82
|
+
}
|
|
83
|
+
}, [state, renderDeferred, pendingState]);
|
|
84
|
+
}
|
|
85
|
+
return (jsx(RouterContext.Provider, { value: router, children: jsx(RouteContext.Provider, { value: state.route, children: jsx(UrlContext.Provider, { value: state.url, children: jsx(ViewTransitionContext.Provider, { value: viewTransitionState, children: children }) }) }) }));
|
|
86
|
+
};
|
|
87
|
+
TransitionsProvider.displayName = 'TinkoffRouterTransitionsProvider';
|
|
88
|
+
|
|
89
|
+
export { TransitionsProvider, startReactTransition };
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
Object.defineProperty(exports, '__esModule', { value: true });
|
|
4
|
+
|
|
5
|
+
var jsxRuntime = require('react/jsx-runtime');
|
|
6
|
+
var react = require('react');
|
|
7
|
+
var reactHooks = require('@tinkoff/react-hooks');
|
|
8
|
+
var core = require('@tramvai/core');
|
|
9
|
+
var context = require('../context.js');
|
|
10
|
+
|
|
11
|
+
let startTransition;
|
|
12
|
+
try {
|
|
13
|
+
// eslint-disable-next-line import/no-unresolved, import/extensions
|
|
14
|
+
startTransition = require('react').startTransition;
|
|
15
|
+
}
|
|
16
|
+
catch { }
|
|
17
|
+
const startReactTransition = typeof startTransition === 'function' && process.env.__TRAMVAI_REACT_TRANSITIONS === 'true'
|
|
18
|
+
? startTransition
|
|
19
|
+
: (fn) => {
|
|
20
|
+
fn();
|
|
21
|
+
};
|
|
22
|
+
const TransitionsProvider = ({ router, children, }) => {
|
|
23
|
+
// fallback to outdated router implementation
|
|
24
|
+
const [state, setState] = react.useState(() => {
|
|
25
|
+
var _a, _b;
|
|
26
|
+
return ({
|
|
27
|
+
route: (_a = router.getLastRoute()) !== null && _a !== void 0 ? _a : router.getCurrentRoute(),
|
|
28
|
+
url: (_b = router.getLastUrl()) !== null && _b !== void 0 ? _b : router.getCurrentUrl(),
|
|
29
|
+
});
|
|
30
|
+
});
|
|
31
|
+
// Router state that will be applied to the DOM.
|
|
32
|
+
const [pendingState, setPendingState] = react.useState(null);
|
|
33
|
+
// View transition state for context.
|
|
34
|
+
const [viewTransitionState, setViewTransitionState] = react.useState({
|
|
35
|
+
isTransitioning: false,
|
|
36
|
+
});
|
|
37
|
+
// Deferred render promise, that resolves when the DOM updates after a navigation.
|
|
38
|
+
const [renderDeferred, setRenderDeferred] = react.useState(null);
|
|
39
|
+
const updateRouterState = (navigation) => {
|
|
40
|
+
if (document.startViewTransition !== undefined && navigation.viewTransition) {
|
|
41
|
+
setPendingState({ route: navigation.to, url: navigation.url });
|
|
42
|
+
setViewTransitionState({
|
|
43
|
+
isTransitioning: true,
|
|
44
|
+
currentRoute: navigation.from,
|
|
45
|
+
nextRoute: navigation.to,
|
|
46
|
+
});
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
49
|
+
startReactTransition(() => {
|
|
50
|
+
setState({ route: navigation.to, url: navigation.url });
|
|
51
|
+
});
|
|
52
|
+
};
|
|
53
|
+
reactHooks.useIsomorphicLayoutEffect(() => router.registerSyncHook('change', updateRouterState), [router]);
|
|
54
|
+
// It's okay to ignore rules of hooks here, because this code
|
|
55
|
+
// will not be a part of the bundle in case of truly condition
|
|
56
|
+
if (process.env.__TRAMVAI_VIEW_TRANSITIONS === 'true') {
|
|
57
|
+
// eslint-disable-next-line react-hooks/rules-of-hooks
|
|
58
|
+
react.useEffect(() => {
|
|
59
|
+
if (viewTransitionState.isTransitioning) {
|
|
60
|
+
setRenderDeferred(new core.Deferred());
|
|
61
|
+
}
|
|
62
|
+
}, [viewTransitionState.isTransitioning]);
|
|
63
|
+
// eslint-disable-next-line react-hooks/rules-of-hooks
|
|
64
|
+
react.useEffect(() => {
|
|
65
|
+
if (renderDeferred !== null && pendingState !== null) {
|
|
66
|
+
const transition = document.startViewTransition(async () => {
|
|
67
|
+
startReactTransition(() => {
|
|
68
|
+
setState(pendingState);
|
|
69
|
+
});
|
|
70
|
+
await renderDeferred.promise;
|
|
71
|
+
});
|
|
72
|
+
// eslint-disable-next-line promise/catch-or-return
|
|
73
|
+
transition.finished.finally(() => {
|
|
74
|
+
setRenderDeferred(null);
|
|
75
|
+
setPendingState(null);
|
|
76
|
+
setViewTransitionState({ isTransitioning: false });
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
}, [pendingState, renderDeferred]);
|
|
80
|
+
// eslint-disable-next-line react-hooks/rules-of-hooks
|
|
81
|
+
react.useEffect(() => {
|
|
82
|
+
if (renderDeferred !== null &&
|
|
83
|
+
pendingState !== null &&
|
|
84
|
+
state.route.actualPath === pendingState.route.actualPath) {
|
|
85
|
+
renderDeferred.resolve();
|
|
86
|
+
}
|
|
87
|
+
}, [state, renderDeferred, pendingState]);
|
|
88
|
+
}
|
|
89
|
+
return (jsxRuntime.jsx(context.RouterContext.Provider, { value: router, children: jsxRuntime.jsx(context.RouteContext.Provider, { value: state.route, children: jsxRuntime.jsx(context.UrlContext.Provider, { value: state.url, children: jsxRuntime.jsx(context.ViewTransitionContext.Provider, { value: viewTransitionState, children: children }) }) }) }));
|
|
90
|
+
};
|
|
91
|
+
TransitionsProvider.displayName = 'TinkoffRouterTransitionsProvider';
|
|
92
|
+
|
|
93
|
+
exports.TransitionsProvider = TransitionsProvider;
|
|
94
|
+
exports.startReactTransition = startReactTransition;
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { Url } from '@tinkoff/url';
|
|
2
|
+
import type { PropsWithChildren } from 'react';
|
|
3
|
+
import type { NavigationRoute } from '../../../types';
|
|
4
|
+
import type { AbstractRouter } from '../../../router/abstract';
|
|
5
|
+
export interface RouterState {
|
|
6
|
+
route: NavigationRoute;
|
|
7
|
+
url: Url;
|
|
8
|
+
}
|
|
9
|
+
export interface RouterProviderProps extends PropsWithChildren {
|
|
10
|
+
router: AbstractRouter;
|
|
11
|
+
serverState?: {
|
|
12
|
+
route: NavigationRoute;
|
|
13
|
+
url: Url;
|
|
14
|
+
};
|
|
15
|
+
}
|
|
16
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { useContext } from 'react';
|
|
2
|
+
import { ViewTransitionContext, RouterContext } from './context.browser.js';
|
|
3
|
+
|
|
4
|
+
const normalizePath = (payload, router) => {
|
|
5
|
+
const route = router.resolve(payload);
|
|
6
|
+
const resolvedPath = route.redirect !== undefined ? router.resolve(route.redirect).actualPath : route.actualPath;
|
|
7
|
+
return resolvedPath.endsWith('/') ? resolvedPath : `${resolvedPath}/`;
|
|
8
|
+
};
|
|
9
|
+
const useViewTransition = (payload) => {
|
|
10
|
+
const viewTransition = useContext(ViewTransitionContext);
|
|
11
|
+
const router = useContext(RouterContext);
|
|
12
|
+
if (viewTransition.isTransitioning) {
|
|
13
|
+
const path = normalizePath(payload, router);
|
|
14
|
+
const currentPath = viewTransition.currentRoute.actualPath;
|
|
15
|
+
const nextPath = viewTransition.nextRoute.actualPath;
|
|
16
|
+
// We are handling forward and back navigations for the same route here
|
|
17
|
+
return [currentPath, nextPath].includes(path);
|
|
18
|
+
}
|
|
19
|
+
return false;
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
export { useViewTransition };
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { useContext } from 'react';
|
|
2
|
+
import { ViewTransitionContext, RouterContext } from './context.es.js';
|
|
3
|
+
|
|
4
|
+
const normalizePath = (payload, router) => {
|
|
5
|
+
const route = router.resolve(payload);
|
|
6
|
+
const resolvedPath = route.redirect !== undefined ? router.resolve(route.redirect).actualPath : route.actualPath;
|
|
7
|
+
return resolvedPath.endsWith('/') ? resolvedPath : `${resolvedPath}/`;
|
|
8
|
+
};
|
|
9
|
+
const useViewTransition = (payload) => {
|
|
10
|
+
const viewTransition = useContext(ViewTransitionContext);
|
|
11
|
+
const router = useContext(RouterContext);
|
|
12
|
+
if (viewTransition.isTransitioning) {
|
|
13
|
+
const path = normalizePath(payload, router);
|
|
14
|
+
const currentPath = viewTransition.currentRoute.actualPath;
|
|
15
|
+
const nextPath = viewTransition.nextRoute.actualPath;
|
|
16
|
+
// We are handling forward and back navigations for the same route here
|
|
17
|
+
return [currentPath, nextPath].includes(path);
|
|
18
|
+
}
|
|
19
|
+
return false;
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
export { useViewTransition };
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
Object.defineProperty(exports, '__esModule', { value: true });
|
|
4
|
+
|
|
5
|
+
var react = require('react');
|
|
6
|
+
var context = require('./context.js');
|
|
7
|
+
|
|
8
|
+
const normalizePath = (payload, router) => {
|
|
9
|
+
const route = router.resolve(payload);
|
|
10
|
+
const resolvedPath = route.redirect !== undefined ? router.resolve(route.redirect).actualPath : route.actualPath;
|
|
11
|
+
return resolvedPath.endsWith('/') ? resolvedPath : `${resolvedPath}/`;
|
|
12
|
+
};
|
|
13
|
+
const useViewTransition = (payload) => {
|
|
14
|
+
const viewTransition = react.useContext(context.ViewTransitionContext);
|
|
15
|
+
const router = react.useContext(context.RouterContext);
|
|
16
|
+
if (viewTransition.isTransitioning) {
|
|
17
|
+
const path = normalizePath(payload, router);
|
|
18
|
+
const currentPath = viewTransition.currentRoute.actualPath;
|
|
19
|
+
const nextPath = viewTransition.nextRoute.actualPath;
|
|
20
|
+
// We are handling forward and back navigations for the same route here
|
|
21
|
+
return [currentPath, nextPath].includes(path);
|
|
22
|
+
}
|
|
23
|
+
return false;
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
exports.useViewTransition = useViewTransition;
|
package/lib/index.browser.js
CHANGED
|
@@ -3,10 +3,11 @@ export { NoSpaRouter } from './router/clientNoSpa.browser.js';
|
|
|
3
3
|
export { AbstractRouter } from './router/abstract.browser.js';
|
|
4
4
|
export { History } from './history/base.browser.js';
|
|
5
5
|
export { logger, setLogger } from './logger.browser.js';
|
|
6
|
-
export { Provider } from './components/react/
|
|
6
|
+
export { Provider } from './components/react/index.browser.js';
|
|
7
|
+
export { RouteTree } from './tree/tree.browser.js';
|
|
8
|
+
export { getParts, isHistoryFallback, isParameterized, isWildcard, makePath, parse } from './tree/utils.browser.js';
|
|
7
9
|
export { useRouter } from './components/react/useRouter.browser.js';
|
|
8
10
|
export { useRoute } from './components/react/useRoute.browser.js';
|
|
9
11
|
export { useUrl } from './components/react/useUrl.browser.js';
|
|
10
12
|
export { useNavigate } from './components/react/useNavigate.browser.js';
|
|
11
|
-
export {
|
|
12
|
-
export { getParts, isHistoryFallback, isParameterized, isWildcard, makePath, parse } from './tree/utils.browser.js';
|
|
13
|
+
export { useViewTransition } from './components/react/useViewTransition.browser.js';
|
package/lib/index.es.js
CHANGED
|
@@ -3,10 +3,11 @@ export { NoSpaRouter } from './router/clientNoSpa.es.js';
|
|
|
3
3
|
export { AbstractRouter } from './router/abstract.es.js';
|
|
4
4
|
export { History } from './history/base.es.js';
|
|
5
5
|
export { logger, setLogger } from './logger.es.js';
|
|
6
|
-
export { Provider } from './components/react/
|
|
6
|
+
export { Provider } from './components/react/index.es.js';
|
|
7
|
+
export { RouteTree } from './tree/tree.es.js';
|
|
8
|
+
export { getParts, isHistoryFallback, isParameterized, isWildcard, makePath, parse } from './tree/utils.es.js';
|
|
7
9
|
export { useRouter } from './components/react/useRouter.es.js';
|
|
8
10
|
export { useRoute } from './components/react/useRoute.es.js';
|
|
9
11
|
export { useUrl } from './components/react/useUrl.es.js';
|
|
10
12
|
export { useNavigate } from './components/react/useNavigate.es.js';
|
|
11
|
-
export {
|
|
12
|
-
export { getParts, isHistoryFallback, isParameterized, isWildcard, makePath, parse } from './tree/utils.es.js';
|
|
13
|
+
export { useViewTransition } from './components/react/useViewTransition.es.js';
|
package/lib/index.js
CHANGED
|
@@ -7,13 +7,14 @@ var clientNoSpa = require('./router/clientNoSpa.js');
|
|
|
7
7
|
var abstract = require('./router/abstract.js');
|
|
8
8
|
var base = require('./history/base.js');
|
|
9
9
|
var logger = require('./logger.js');
|
|
10
|
-
var
|
|
10
|
+
var index = require('./components/react/index.js');
|
|
11
|
+
var tree = require('./tree/tree.js');
|
|
12
|
+
var utils = require('./tree/utils.js');
|
|
11
13
|
var useRouter = require('./components/react/useRouter.js');
|
|
12
14
|
var useRoute = require('./components/react/useRoute.js');
|
|
13
15
|
var useUrl = require('./components/react/useUrl.js');
|
|
14
16
|
var useNavigate = require('./components/react/useNavigate.js');
|
|
15
|
-
var
|
|
16
|
-
var utils = require('./tree/utils.js');
|
|
17
|
+
var useViewTransition = require('./components/react/useViewTransition.js');
|
|
17
18
|
|
|
18
19
|
|
|
19
20
|
|
|
@@ -26,11 +27,7 @@ Object.defineProperty(exports, 'logger', {
|
|
|
26
27
|
get: function () { return logger.logger; }
|
|
27
28
|
});
|
|
28
29
|
exports.setLogger = logger.setLogger;
|
|
29
|
-
exports.Provider =
|
|
30
|
-
exports.useRouter = useRouter.useRouter;
|
|
31
|
-
exports.useRoute = useRoute.useRoute;
|
|
32
|
-
exports.useUrl = useUrl.useUrl;
|
|
33
|
-
exports.useNavigate = useNavigate.useNavigate;
|
|
30
|
+
exports.Provider = index.Provider;
|
|
34
31
|
exports.RouteTree = tree.RouteTree;
|
|
35
32
|
exports.getParts = utils.getParts;
|
|
36
33
|
exports.isHistoryFallback = utils.isHistoryFallback;
|
|
@@ -38,3 +35,8 @@ exports.isParameterized = utils.isParameterized;
|
|
|
38
35
|
exports.isWildcard = utils.isWildcard;
|
|
39
36
|
exports.makePath = utils.makePath;
|
|
40
37
|
exports.parse = utils.parse;
|
|
38
|
+
exports.useRouter = useRouter.useRouter;
|
|
39
|
+
exports.useRoute = useRoute.useRoute;
|
|
40
|
+
exports.useUrl = useUrl.useUrl;
|
|
41
|
+
exports.useNavigate = useNavigate.useNavigate;
|
|
42
|
+
exports.useViewTransition = useViewTransition.useViewTransition;
|
|
@@ -6,14 +6,16 @@ import { logger } from '../logger.browser.js';
|
|
|
6
6
|
import { makeNavigateOptions, normalizeManySlashes, normalizeTrailingSlash, isSameHost, registerHook } from '../utils.browser.js';
|
|
7
7
|
|
|
8
8
|
class AbstractRouter {
|
|
9
|
-
constructor({ trailingSlash, mergeSlashes, beforeResolve = [], beforeNavigate = [], afterNavigate = [], beforeUpdateCurrent = [], afterUpdateCurrent = [], guards = [], onChange = [], onRedirect, onNotFound, onBlock, }) {
|
|
9
|
+
constructor({ trailingSlash, mergeSlashes, enableViewTransitions, beforeResolve = [], beforeNavigate = [], afterNavigate = [], beforeUpdateCurrent = [], afterUpdateCurrent = [], guards = [], onChange = [], onRedirect, onNotFound, onBlock, }) {
|
|
10
10
|
this.started = false;
|
|
11
11
|
this.trailingSlash = false;
|
|
12
12
|
this.strictTrailingSlash = true;
|
|
13
|
+
this.viewTransitionsEnabled = false;
|
|
13
14
|
this.mergeSlashes = false;
|
|
14
15
|
this.trailingSlash = trailingSlash !== null && trailingSlash !== void 0 ? trailingSlash : false;
|
|
15
16
|
this.strictTrailingSlash = typeof trailingSlash === 'undefined';
|
|
16
17
|
this.mergeSlashes = mergeSlashes !== null && mergeSlashes !== void 0 ? mergeSlashes : false;
|
|
18
|
+
this.viewTransitionsEnabled = enableViewTransitions !== null && enableViewTransitions !== void 0 ? enableViewTransitions : false;
|
|
17
19
|
this.hooks = new Map([
|
|
18
20
|
['beforeResolve', new Set(beforeResolve)],
|
|
19
21
|
['beforeNavigate', new Set(beforeNavigate)],
|
|
@@ -108,7 +110,7 @@ class AbstractRouter {
|
|
|
108
110
|
}
|
|
109
111
|
async internalNavigate(navigateOptions, { history, redirect }) {
|
|
110
112
|
var _a;
|
|
111
|
-
const { url, replace, params, navigateState, code } = navigateOptions;
|
|
113
|
+
const { url, replace, params, navigateState, code, viewTransition } = navigateOptions;
|
|
112
114
|
const prevNavigation = redirect
|
|
113
115
|
? this.lastNavigation
|
|
114
116
|
: (_a = this.currentNavigation) !== null && _a !== void 0 ? _a : this.lastNavigation;
|
|
@@ -131,6 +133,9 @@ class AbstractRouter {
|
|
|
131
133
|
redirectFrom,
|
|
132
134
|
key: this.uuid(),
|
|
133
135
|
};
|
|
136
|
+
if (this.viewTransitionsEnabled && viewTransition !== undefined) {
|
|
137
|
+
navigation.viewTransition = viewTransition;
|
|
138
|
+
}
|
|
134
139
|
await this.runHooks('beforeResolve', navigation);
|
|
135
140
|
const to = this.resolveRoute({ url: resolvedUrl, params, navigateState }, { wildcard: true });
|
|
136
141
|
if (to) {
|
|
@@ -168,7 +173,7 @@ class AbstractRouter {
|
|
|
168
173
|
}
|
|
169
174
|
}
|
|
170
175
|
resolve(resolveOptions, options) {
|
|
171
|
-
const opts =
|
|
176
|
+
const opts = makeNavigateOptions(resolveOptions);
|
|
172
177
|
return this.resolveRoute({ ...opts, url: parse(opts.url) }, options);
|
|
173
178
|
}
|
|
174
179
|
back(options) {
|
package/lib/router/abstract.d.ts
CHANGED
|
@@ -5,6 +5,7 @@ import type { RouteTree } from '../tree/tree';
|
|
|
5
5
|
export interface Options {
|
|
6
6
|
trailingSlash?: boolean;
|
|
7
7
|
mergeSlashes?: boolean;
|
|
8
|
+
enableViewTransitions?: boolean;
|
|
8
9
|
routes?: Route[];
|
|
9
10
|
onRedirect?: NavigationHook;
|
|
10
11
|
onNotFound?: NavigationHook;
|
|
@@ -26,6 +27,7 @@ export declare abstract class AbstractRouter {
|
|
|
26
27
|
protected started: boolean;
|
|
27
28
|
protected trailingSlash: boolean;
|
|
28
29
|
protected strictTrailingSlash: boolean;
|
|
30
|
+
protected viewTransitionsEnabled: boolean;
|
|
29
31
|
protected mergeSlashes: boolean;
|
|
30
32
|
protected currentNavigation: Navigation;
|
|
31
33
|
protected lastNavigation: Navigation;
|
|
@@ -35,7 +37,7 @@ export declare abstract class AbstractRouter {
|
|
|
35
37
|
protected hooks: Map<HookName, Set<NavigationHook>>;
|
|
36
38
|
protected syncHooks: Map<SyncHookName, Set<NavigationSyncHook>>;
|
|
37
39
|
private currentUuid;
|
|
38
|
-
constructor({ trailingSlash, mergeSlashes, beforeResolve, beforeNavigate, afterNavigate, beforeUpdateCurrent, afterUpdateCurrent, guards, onChange, onRedirect, onNotFound, onBlock, }: Options);
|
|
40
|
+
constructor({ trailingSlash, mergeSlashes, enableViewTransitions, beforeResolve, beforeNavigate, afterNavigate, beforeUpdateCurrent, afterUpdateCurrent, guards, onChange, onRedirect, onNotFound, onBlock, }: Options);
|
|
39
41
|
protected onRedirect?: NavigationHook;
|
|
40
42
|
protected onNotFound?: NavigationHook;
|
|
41
43
|
protected onBlock?: NavigationHook;
|
|
@@ -6,14 +6,16 @@ import { logger } from '../logger.es.js';
|
|
|
6
6
|
import { makeNavigateOptions, normalizeManySlashes, normalizeTrailingSlash, isSameHost, registerHook } from '../utils.es.js';
|
|
7
7
|
|
|
8
8
|
class AbstractRouter {
|
|
9
|
-
constructor({ trailingSlash, mergeSlashes, beforeResolve = [], beforeNavigate = [], afterNavigate = [], beforeUpdateCurrent = [], afterUpdateCurrent = [], guards = [], onChange = [], onRedirect, onNotFound, onBlock, }) {
|
|
9
|
+
constructor({ trailingSlash, mergeSlashes, enableViewTransitions, beforeResolve = [], beforeNavigate = [], afterNavigate = [], beforeUpdateCurrent = [], afterUpdateCurrent = [], guards = [], onChange = [], onRedirect, onNotFound, onBlock, }) {
|
|
10
10
|
this.started = false;
|
|
11
11
|
this.trailingSlash = false;
|
|
12
12
|
this.strictTrailingSlash = true;
|
|
13
|
+
this.viewTransitionsEnabled = false;
|
|
13
14
|
this.mergeSlashes = false;
|
|
14
15
|
this.trailingSlash = trailingSlash !== null && trailingSlash !== void 0 ? trailingSlash : false;
|
|
15
16
|
this.strictTrailingSlash = typeof trailingSlash === 'undefined';
|
|
16
17
|
this.mergeSlashes = mergeSlashes !== null && mergeSlashes !== void 0 ? mergeSlashes : false;
|
|
18
|
+
this.viewTransitionsEnabled = enableViewTransitions !== null && enableViewTransitions !== void 0 ? enableViewTransitions : false;
|
|
17
19
|
this.hooks = new Map([
|
|
18
20
|
['beforeResolve', new Set(beforeResolve)],
|
|
19
21
|
['beforeNavigate', new Set(beforeNavigate)],
|
|
@@ -108,7 +110,7 @@ class AbstractRouter {
|
|
|
108
110
|
}
|
|
109
111
|
async internalNavigate(navigateOptions, { history, redirect }) {
|
|
110
112
|
var _a;
|
|
111
|
-
const { url, replace, params, navigateState, code } = navigateOptions;
|
|
113
|
+
const { url, replace, params, navigateState, code, viewTransition } = navigateOptions;
|
|
112
114
|
const prevNavigation = redirect
|
|
113
115
|
? this.lastNavigation
|
|
114
116
|
: (_a = this.currentNavigation) !== null && _a !== void 0 ? _a : this.lastNavigation;
|
|
@@ -131,6 +133,9 @@ class AbstractRouter {
|
|
|
131
133
|
redirectFrom,
|
|
132
134
|
key: this.uuid(),
|
|
133
135
|
};
|
|
136
|
+
if (this.viewTransitionsEnabled && viewTransition !== undefined) {
|
|
137
|
+
navigation.viewTransition = viewTransition;
|
|
138
|
+
}
|
|
134
139
|
await this.runHooks('beforeResolve', navigation);
|
|
135
140
|
const to = this.resolveRoute({ url: resolvedUrl, params, navigateState }, { wildcard: true });
|
|
136
141
|
if (to) {
|
|
@@ -168,7 +173,7 @@ class AbstractRouter {
|
|
|
168
173
|
}
|
|
169
174
|
}
|
|
170
175
|
resolve(resolveOptions, options) {
|
|
171
|
-
const opts =
|
|
176
|
+
const opts = makeNavigateOptions(resolveOptions);
|
|
172
177
|
return this.resolveRoute({ ...opts, url: parse(opts.url) }, options);
|
|
173
178
|
}
|
|
174
179
|
back(options) {
|
package/lib/router/abstract.js
CHANGED
|
@@ -15,14 +15,16 @@ var isString__default = /*#__PURE__*/_interopDefaultLegacy(isString);
|
|
|
15
15
|
var isObject__default = /*#__PURE__*/_interopDefaultLegacy(isObject);
|
|
16
16
|
|
|
17
17
|
class AbstractRouter {
|
|
18
|
-
constructor({ trailingSlash, mergeSlashes, beforeResolve = [], beforeNavigate = [], afterNavigate = [], beforeUpdateCurrent = [], afterUpdateCurrent = [], guards = [], onChange = [], onRedirect, onNotFound, onBlock, }) {
|
|
18
|
+
constructor({ trailingSlash, mergeSlashes, enableViewTransitions, beforeResolve = [], beforeNavigate = [], afterNavigate = [], beforeUpdateCurrent = [], afterUpdateCurrent = [], guards = [], onChange = [], onRedirect, onNotFound, onBlock, }) {
|
|
19
19
|
this.started = false;
|
|
20
20
|
this.trailingSlash = false;
|
|
21
21
|
this.strictTrailingSlash = true;
|
|
22
|
+
this.viewTransitionsEnabled = false;
|
|
22
23
|
this.mergeSlashes = false;
|
|
23
24
|
this.trailingSlash = trailingSlash !== null && trailingSlash !== void 0 ? trailingSlash : false;
|
|
24
25
|
this.strictTrailingSlash = typeof trailingSlash === 'undefined';
|
|
25
26
|
this.mergeSlashes = mergeSlashes !== null && mergeSlashes !== void 0 ? mergeSlashes : false;
|
|
27
|
+
this.viewTransitionsEnabled = enableViewTransitions !== null && enableViewTransitions !== void 0 ? enableViewTransitions : false;
|
|
26
28
|
this.hooks = new Map([
|
|
27
29
|
['beforeResolve', new Set(beforeResolve)],
|
|
28
30
|
['beforeNavigate', new Set(beforeNavigate)],
|
|
@@ -117,7 +119,7 @@ class AbstractRouter {
|
|
|
117
119
|
}
|
|
118
120
|
async internalNavigate(navigateOptions, { history, redirect }) {
|
|
119
121
|
var _a;
|
|
120
|
-
const { url, replace, params, navigateState, code } = navigateOptions;
|
|
122
|
+
const { url, replace, params, navigateState, code, viewTransition } = navigateOptions;
|
|
121
123
|
const prevNavigation = redirect
|
|
122
124
|
? this.lastNavigation
|
|
123
125
|
: (_a = this.currentNavigation) !== null && _a !== void 0 ? _a : this.lastNavigation;
|
|
@@ -140,6 +142,9 @@ class AbstractRouter {
|
|
|
140
142
|
redirectFrom,
|
|
141
143
|
key: this.uuid(),
|
|
142
144
|
};
|
|
145
|
+
if (this.viewTransitionsEnabled && viewTransition !== undefined) {
|
|
146
|
+
navigation.viewTransition = viewTransition;
|
|
147
|
+
}
|
|
143
148
|
await this.runHooks('beforeResolve', navigation);
|
|
144
149
|
const to = this.resolveRoute({ url: resolvedUrl, params, navigateState }, { wildcard: true });
|
|
145
150
|
if (to) {
|
|
@@ -177,7 +182,7 @@ class AbstractRouter {
|
|
|
177
182
|
}
|
|
178
183
|
}
|
|
179
184
|
resolve(resolveOptions, options) {
|
|
180
|
-
const opts =
|
|
185
|
+
const opts = utils.makeNavigateOptions(resolveOptions);
|
|
181
186
|
return this.resolveRoute({ ...opts, url: url.parse(opts.url) }, options);
|
|
182
187
|
}
|
|
183
188
|
back(options) {
|
|
@@ -3,11 +3,18 @@ import { logger } from '../logger.browser.js';
|
|
|
3
3
|
import { RouteTree } from '../tree/tree.browser.js';
|
|
4
4
|
|
|
5
5
|
const DELAY_CHECK_INTERVAL = 50;
|
|
6
|
+
const APPLIED_VIEW_TRANSITIONS_KEY = '_t_view_transitions';
|
|
6
7
|
class Router extends ClientRouter {
|
|
7
8
|
constructor(options) {
|
|
8
9
|
super(options);
|
|
9
10
|
this.tree = new RouteTree(options.routes);
|
|
10
11
|
this.history.setTree(this.tree);
|
|
12
|
+
if (this.viewTransitionsEnabled) {
|
|
13
|
+
this.restoreAppliedViewTransitions();
|
|
14
|
+
window.addEventListener('pagehide', () => {
|
|
15
|
+
this.saveAppliedViewTransitions();
|
|
16
|
+
});
|
|
17
|
+
}
|
|
11
18
|
}
|
|
12
19
|
async rehydrate(navigation) {
|
|
13
20
|
return this.resolveIfDelayFound(super.rehydrate(navigation));
|
|
@@ -20,7 +27,11 @@ class Router extends ClientRouter {
|
|
|
20
27
|
return this.flattenDelayedNavigation(delayedNavigation);
|
|
21
28
|
}
|
|
22
29
|
}
|
|
23
|
-
async run(
|
|
30
|
+
async run(payload) {
|
|
31
|
+
const navigation = { ...payload };
|
|
32
|
+
if (this.viewTransitionsEnabled) {
|
|
33
|
+
navigation.viewTransition = this.shouldApplyViewTransition(payload);
|
|
34
|
+
}
|
|
24
35
|
// if router is not started yet delay current navigation without blocking promise resolving
|
|
25
36
|
if (!this.started) {
|
|
26
37
|
this.delayNavigation(navigation);
|
|
@@ -161,6 +172,60 @@ class Router extends ClientRouter {
|
|
|
161
172
|
this.delayedReject = null;
|
|
162
173
|
});
|
|
163
174
|
}
|
|
175
|
+
shouldApplyViewTransition(navigation) {
|
|
176
|
+
var _a, _b;
|
|
177
|
+
const from = navigation.from.actualPath;
|
|
178
|
+
const to = navigation.to.redirect !== undefined
|
|
179
|
+
? (_a = this.resolve(navigation.to.redirect)) === null || _a === void 0 ? void 0 : _a.actualPath
|
|
180
|
+
: (_b = navigation.to) === null || _b === void 0 ? void 0 : _b.actualPath;
|
|
181
|
+
// If View Transition enabled to current navigation, save it
|
|
182
|
+
if (navigation.viewTransition) {
|
|
183
|
+
const toPaths = this.appliedViewTransitions.get(from);
|
|
184
|
+
if (this.appliedViewTransitions.get(from)) {
|
|
185
|
+
toPaths.add(to);
|
|
186
|
+
}
|
|
187
|
+
else {
|
|
188
|
+
this.appliedViewTransitions.set(from, new Set([to]));
|
|
189
|
+
}
|
|
190
|
+
return true;
|
|
191
|
+
}
|
|
192
|
+
const priorPaths = this.appliedViewTransitions.get(from);
|
|
193
|
+
// If we were on the route with view transition previously,
|
|
194
|
+
// then there must be back navigation with view transition also
|
|
195
|
+
if (priorPaths !== undefined && priorPaths.has(to)) {
|
|
196
|
+
return true;
|
|
197
|
+
}
|
|
198
|
+
// If we don't have a previous forward navigation, assume we are
|
|
199
|
+
// going back from the new route and enable view transition if it
|
|
200
|
+
// was previously enabled
|
|
201
|
+
return this.appliedViewTransitions.has(to);
|
|
202
|
+
}
|
|
203
|
+
restoreAppliedViewTransitions() {
|
|
204
|
+
try {
|
|
205
|
+
const valueFromStorage = sessionStorage.getItem(APPLIED_VIEW_TRANSITIONS_KEY);
|
|
206
|
+
if (valueFromStorage !== null) {
|
|
207
|
+
const parsedValue = JSON.parse(valueFromStorage);
|
|
208
|
+
this.appliedViewTransitions = new Map(Object.entries(parsedValue).map(([key, value]) => [key, new Set(value)]));
|
|
209
|
+
return;
|
|
210
|
+
}
|
|
211
|
+
this.appliedViewTransitions = new Map();
|
|
212
|
+
}
|
|
213
|
+
catch (error) {
|
|
214
|
+
this.appliedViewTransitions = new Map();
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
saveAppliedViewTransitions() {
|
|
218
|
+
if (this.appliedViewTransitions.size > 0) {
|
|
219
|
+
try {
|
|
220
|
+
const valueToSave = {};
|
|
221
|
+
for (const [key, value] of this.appliedViewTransitions) {
|
|
222
|
+
valueToSave[key] = [...value];
|
|
223
|
+
}
|
|
224
|
+
sessionStorage.setItem(APPLIED_VIEW_TRANSITIONS_KEY, JSON.stringify(valueToSave));
|
|
225
|
+
}
|
|
226
|
+
catch (error) { }
|
|
227
|
+
}
|
|
228
|
+
}
|
|
164
229
|
}
|
|
165
230
|
|
|
166
231
|
export { Router };
|
package/lib/router/browser.d.ts
CHANGED
|
@@ -6,15 +6,19 @@ export declare class Router extends ClientRouter {
|
|
|
6
6
|
protected delayedPromise: Promise<void>;
|
|
7
7
|
protected delayedResolve: () => void;
|
|
8
8
|
protected delayedReject: (error: Error) => void;
|
|
9
|
+
private appliedViewTransitions;
|
|
9
10
|
constructor(options: Options);
|
|
10
11
|
rehydrate(navigation: Navigation): Promise<void>;
|
|
11
12
|
start(): Promise<any>;
|
|
12
|
-
protected run(
|
|
13
|
+
protected run(payload: Navigation): Promise<any>;
|
|
13
14
|
protected delayNavigation(navigation: Navigation): Promise<void>;
|
|
14
15
|
protected commitNavigation(navigation: Navigation): void;
|
|
15
16
|
protected runGuards(navigation: Navigation): Promise<void>;
|
|
16
17
|
protected runHooks(hookName: HookName, navigation: Navigation): Promise<void>;
|
|
17
18
|
private resolveIfDelayFound;
|
|
18
19
|
private flattenDelayedNavigation;
|
|
20
|
+
private shouldApplyViewTransition;
|
|
21
|
+
private restoreAppliedViewTransitions;
|
|
22
|
+
private saveAppliedViewTransitions;
|
|
19
23
|
}
|
|
20
24
|
//# sourceMappingURL=browser.d.ts.map
|
package/lib/types.d.ts
CHANGED
|
@@ -26,6 +26,7 @@ export interface BaseNavigateOptions {
|
|
|
26
26
|
}
|
|
27
27
|
export interface NavigateOptions extends BaseNavigateOptions {
|
|
28
28
|
url?: string;
|
|
29
|
+
viewTransition?: boolean;
|
|
29
30
|
}
|
|
30
31
|
export type UpdateCurrentRouteOptions = BaseNavigateOptions;
|
|
31
32
|
export interface HistoryOptions {
|
|
@@ -50,6 +51,10 @@ export interface Navigation {
|
|
|
50
51
|
* Defines internally within the router
|
|
51
52
|
*/
|
|
52
53
|
key?: number;
|
|
54
|
+
/**
|
|
55
|
+
* Activates View Transition to this navigation
|
|
56
|
+
*/
|
|
57
|
+
viewTransition?: boolean;
|
|
53
58
|
}
|
|
54
59
|
export type NavigationGuard = (navigation: Navigation) => Promise<void | NavigateOptions | string | boolean>;
|
|
55
60
|
export type NavigationHook = (navigation: Navigation) => Promise<void>;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tinkoff/router",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.8",
|
|
4
4
|
"description": "router",
|
|
5
5
|
"main": "lib/index.js",
|
|
6
6
|
"typings": "lib/index.d.ts",
|
|
@@ -27,6 +27,7 @@
|
|
|
27
27
|
"use-sync-external-store": "^1.2.0"
|
|
28
28
|
},
|
|
29
29
|
"peerDependencies": {
|
|
30
|
+
"@tramvai/core": "3.9.0",
|
|
30
31
|
"react": ">=16.14.0",
|
|
31
32
|
"tslib": "^2.4.0"
|
|
32
33
|
},
|
|
@@ -1,16 +0,0 @@
|
|
|
1
|
-
import { jsx } from 'react/jsx-runtime';
|
|
2
|
-
import { useMemo } from 'react';
|
|
3
|
-
import { useSyncExternalStore } from 'use-sync-external-store/shim';
|
|
4
|
-
import { RouterContext, RouteContext, UrlContext } from './context.browser.js';
|
|
5
|
-
|
|
6
|
-
const Provider = ({ router, serverState, children }) => {
|
|
7
|
-
// fallback to outdate router implementation
|
|
8
|
-
const lastRoute = useMemo(() => router.getCurrentRoute(), [router]);
|
|
9
|
-
const lastUrl = useMemo(() => router.getCurrentUrl(), [router]);
|
|
10
|
-
const route = useSyncExternalStore((cb) => router.registerSyncHook('change', cb), () => { var _a, _b; return (_b = (_a = router.getLastRoute) === null || _a === void 0 ? void 0 : _a.call(router)) !== null && _b !== void 0 ? _b : lastRoute; }, serverState ? () => serverState.currentRoute : () => { var _a, _b; return (_b = (_a = router.getLastRoute) === null || _a === void 0 ? void 0 : _a.call(router)) !== null && _b !== void 0 ? _b : lastRoute; });
|
|
11
|
-
const url = useSyncExternalStore((cb) => router.registerSyncHook('change', cb), () => { var _a, _b; return (_b = (_a = router.getLastUrl) === null || _a === void 0 ? void 0 : _a.call(router)) !== null && _b !== void 0 ? _b : lastUrl; }, serverState ? () => serverState.currentUrl : () => { var _a, _b; return (_b = (_a = router.getLastUrl) === null || _a === void 0 ? void 0 : _a.call(router)) !== null && _b !== void 0 ? _b : lastUrl; });
|
|
12
|
-
return (jsx(RouterContext.Provider, { value: router, children: jsx(RouteContext.Provider, { value: route, children: jsx(UrlContext.Provider, { value: url, children: children }) }) }));
|
|
13
|
-
};
|
|
14
|
-
Provider.displayName = 'TinkoffRouterProvider';
|
|
15
|
-
|
|
16
|
-
export { Provider };
|
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
import type { FunctionComponent, ReactNode } from 'react';
|
|
2
|
-
import type { Url } from '@tinkoff/url';
|
|
3
|
-
import type { AbstractRouter } from '../../router/abstract';
|
|
4
|
-
import type { NavigationRoute } from '../../types';
|
|
5
|
-
export declare const Provider: FunctionComponent<{
|
|
6
|
-
router: AbstractRouter;
|
|
7
|
-
serverState?: {
|
|
8
|
-
currentRoute: NavigationRoute;
|
|
9
|
-
currentUrl: Url;
|
|
10
|
-
};
|
|
11
|
-
children?: ReactNode;
|
|
12
|
-
}>;
|
|
13
|
-
//# sourceMappingURL=provider.d.ts.map
|
|
@@ -1,16 +0,0 @@
|
|
|
1
|
-
import { jsx } from 'react/jsx-runtime';
|
|
2
|
-
import { useMemo } from 'react';
|
|
3
|
-
import { useSyncExternalStore } from 'use-sync-external-store/shim';
|
|
4
|
-
import { RouterContext, RouteContext, UrlContext } from './context.es.js';
|
|
5
|
-
|
|
6
|
-
const Provider = ({ router, serverState, children }) => {
|
|
7
|
-
// fallback to outdate router implementation
|
|
8
|
-
const lastRoute = useMemo(() => router.getCurrentRoute(), [router]);
|
|
9
|
-
const lastUrl = useMemo(() => router.getCurrentUrl(), [router]);
|
|
10
|
-
const route = useSyncExternalStore((cb) => router.registerSyncHook('change', cb), () => { var _a, _b; return (_b = (_a = router.getLastRoute) === null || _a === void 0 ? void 0 : _a.call(router)) !== null && _b !== void 0 ? _b : lastRoute; }, serverState ? () => serverState.currentRoute : () => { var _a, _b; return (_b = (_a = router.getLastRoute) === null || _a === void 0 ? void 0 : _a.call(router)) !== null && _b !== void 0 ? _b : lastRoute; });
|
|
11
|
-
const url = useSyncExternalStore((cb) => router.registerSyncHook('change', cb), () => { var _a, _b; return (_b = (_a = router.getLastUrl) === null || _a === void 0 ? void 0 : _a.call(router)) !== null && _b !== void 0 ? _b : lastUrl; }, serverState ? () => serverState.currentUrl : () => { var _a, _b; return (_b = (_a = router.getLastUrl) === null || _a === void 0 ? void 0 : _a.call(router)) !== null && _b !== void 0 ? _b : lastUrl; });
|
|
12
|
-
return (jsx(RouterContext.Provider, { value: router, children: jsx(RouteContext.Provider, { value: route, children: jsx(UrlContext.Provider, { value: url, children: children }) }) }));
|
|
13
|
-
};
|
|
14
|
-
Provider.displayName = 'TinkoffRouterProvider';
|
|
15
|
-
|
|
16
|
-
export { Provider };
|
|
@@ -1,20 +0,0 @@
|
|
|
1
|
-
'use strict';
|
|
2
|
-
|
|
3
|
-
Object.defineProperty(exports, '__esModule', { value: true });
|
|
4
|
-
|
|
5
|
-
var jsxRuntime = require('react/jsx-runtime');
|
|
6
|
-
var react = require('react');
|
|
7
|
-
var shim = require('use-sync-external-store/shim');
|
|
8
|
-
var context = require('./context.js');
|
|
9
|
-
|
|
10
|
-
const Provider = ({ router, serverState, children }) => {
|
|
11
|
-
// fallback to outdate router implementation
|
|
12
|
-
const lastRoute = react.useMemo(() => router.getCurrentRoute(), [router]);
|
|
13
|
-
const lastUrl = react.useMemo(() => router.getCurrentUrl(), [router]);
|
|
14
|
-
const route = shim.useSyncExternalStore((cb) => router.registerSyncHook('change', cb), () => { var _a, _b; return (_b = (_a = router.getLastRoute) === null || _a === void 0 ? void 0 : _a.call(router)) !== null && _b !== void 0 ? _b : lastRoute; }, serverState ? () => serverState.currentRoute : () => { var _a, _b; return (_b = (_a = router.getLastRoute) === null || _a === void 0 ? void 0 : _a.call(router)) !== null && _b !== void 0 ? _b : lastRoute; });
|
|
15
|
-
const url = shim.useSyncExternalStore((cb) => router.registerSyncHook('change', cb), () => { var _a, _b; return (_b = (_a = router.getLastUrl) === null || _a === void 0 ? void 0 : _a.call(router)) !== null && _b !== void 0 ? _b : lastUrl; }, serverState ? () => serverState.currentUrl : () => { var _a, _b; return (_b = (_a = router.getLastUrl) === null || _a === void 0 ? void 0 : _a.call(router)) !== null && _b !== void 0 ? _b : lastUrl; });
|
|
16
|
-
return (jsxRuntime.jsx(context.RouterContext.Provider, { value: router, children: jsxRuntime.jsx(context.RouteContext.Provider, { value: route, children: jsxRuntime.jsx(context.UrlContext.Provider, { value: url, children: children }) }) }));
|
|
17
|
-
};
|
|
18
|
-
Provider.displayName = 'TinkoffRouterProvider';
|
|
19
|
-
|
|
20
|
-
exports.Provider = Provider;
|