navigation-stack 0.5.3 → 0.6.0
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 +16 -0
- package/README.md +144 -282
- package/karma.conf.cjs +1 -1
- package/lib/cjs/NavigationStack.js +138 -49
- package/lib/cjs/data-storage/DataStorage.js +7 -6
- package/lib/cjs/environment/InMemoryEnvironment.js +6 -0
- package/lib/cjs/{session/ServerSideRenderSession.js → environment/ServerSideRenderEnvironment.js} +5 -6
- package/lib/cjs/environment/WebBrowserEnvironment.js +6 -0
- package/lib/cjs/environment/log/InMemoryLog.js +23 -0
- package/lib/cjs/environment/log/WebBrowserLog.js +22 -0
- package/lib/cjs/{session → environment}/navigation/InMemoryNavigation.js +16 -5
- package/lib/cjs/{session → environment}/navigation/ServerSideNavigation.js +16 -7
- package/lib/cjs/{session → environment}/navigation/WebBrowserNavigation.js +48 -8
- package/lib/cjs/{session/navigation/error/ServerSideNavigationError.js → environment/navigation/error/ServerSideRedirectError.js} +2 -2
- package/lib/cjs/environment/scroll-position/WebBrowserScrollPosition.js +15 -0
- package/lib/cjs/getLocationBaseFromLocation.js +14 -0
- package/lib/cjs/getLocationUrl.js +3 -5
- package/lib/cjs/index.js +10 -16
- package/lib/cjs/navigationBlockers.js +34 -32
- package/lib/cjs/navigationBlockersEvaluation.js +150 -0
- package/lib/cjs/parseInputLocation.js +2 -2
- package/lib/cjs/parseQueryFromSearch.js +3 -6
- package/lib/cjs/parseQueryString.js +77 -0
- package/lib/cjs/scroll-position/ScrollPositionAutoSaver.js +7 -6
- package/lib/cjs/scroll-position/ScrollPositionRestoration.js +31 -27
- package/lib/cjs/scroll-position/ScrollPositionSaver.js +6 -4
- package/lib/cjs/session/Session.js +61 -26
- package/lib/cjs/session/subscription/Subscription.js +36 -18
- package/lib/cjs/stringifyQuery.js +66 -0
- package/lib/cjs/stringifyQueryAsSearch.js +14 -0
- package/lib/esm/NavigationStack.js +138 -49
- package/lib/esm/data-storage/DataStorage.js +7 -6
- package/lib/esm/environment/InMemoryEnvironment.js +6 -0
- package/lib/esm/environment/ServerSideRenderEnvironment.js +10 -0
- package/lib/esm/environment/WebBrowserEnvironment.js +6 -0
- package/lib/esm/environment/log/InMemoryLog.js +17 -0
- package/lib/esm/environment/log/WebBrowserLog.js +16 -0
- package/lib/esm/{session → environment}/navigation/InMemoryNavigation.js +16 -5
- package/lib/esm/{session → environment}/navigation/ServerSideNavigation.js +16 -7
- package/lib/esm/{session → environment}/navigation/WebBrowserNavigation.js +48 -8
- package/lib/esm/{session/navigation/error/ServerSideNavigationError.js → environment/navigation/error/ServerSideRedirectError.js} +1 -1
- package/lib/esm/environment/scroll-position/WebBrowserScrollPosition.js +15 -0
- package/lib/esm/getLocationBaseFromLocation.js +9 -0
- package/lib/esm/getLocationUrl.js +2 -5
- package/lib/esm/index.js +5 -8
- package/lib/esm/navigationBlockers.js +34 -32
- package/lib/esm/navigationBlockersEvaluation.js +145 -0
- package/lib/esm/parseInputLocation.js +2 -2
- package/lib/esm/parseQueryFromSearch.js +2 -6
- package/lib/esm/parseQueryString.js +72 -0
- package/lib/esm/scroll-position/ScrollPositionAutoSaver.js +7 -6
- package/lib/esm/scroll-position/ScrollPositionRestoration.js +31 -27
- package/lib/esm/scroll-position/ScrollPositionSaver.js +6 -4
- package/lib/esm/session/Session.js +61 -26
- package/lib/esm/session/subscription/Subscription.js +36 -18
- package/lib/esm/stringifyQuery.js +61 -0
- package/lib/esm/stringifyQueryAsSearch.js +8 -0
- package/lib/index.d.ts +180 -34
- package/package.json +4 -7
- package/src/NavigationStack.js +166 -56
- package/src/data-storage/DataStorage.js +9 -6
- package/src/environment/InMemoryEnvironment.js +6 -0
- package/src/environment/ServerSideRenderEnvironment.js +10 -0
- package/src/environment/WebBrowserEnvironment.js +6 -0
- package/src/environment/log/InMemoryLog.js +20 -0
- package/src/environment/log/WebBrowserLog.js +18 -0
- package/src/{session → environment}/navigation/InMemoryNavigation.js +16 -5
- package/src/{session → environment}/navigation/ServerSideNavigation.js +16 -7
- package/src/{session → environment}/navigation/WebBrowserNavigation.js +48 -8
- package/src/{session/navigation/error/ServerSideNavigationError.js → environment/navigation/error/ServerSideRedirectError.js} +1 -1
- package/src/environment/scroll-position/WebBrowserScrollPosition.js +15 -0
- package/src/getLocationBaseFromLocation.js +7 -0
- package/src/getLocationUrl.js +2 -5
- package/src/index.js +10 -13
- package/src/navigationBlockers.js +55 -34
- package/src/navigationBlockersEvaluation.js +161 -0
- package/src/parseInputLocation.js +2 -2
- package/src/parseQueryFromSearch.js +2 -6
- package/src/parseQueryString.js +81 -0
- package/src/scroll-position/ScrollPositionAutoSaver.js +10 -6
- package/src/scroll-position/ScrollPositionRestoration.js +36 -30
- package/src/scroll-position/ScrollPositionSaver.js +6 -4
- package/src/scroll-position/index.js +1 -1
- package/src/session/Session.js +68 -24
- package/src/session/subscription/Subscription.js +36 -11
- package/src/stringifyQuery.js +71 -0
- package/src/stringifyQueryAsSearch.js +9 -0
- package/test/NavigationStack.addBasePath.test.js +50 -0
- package/test/{redux/middleware/createNonProgrammaticNavigationBlockerMiddleware.test.js → NavigationStack.blockNonProgrammaticNavigationIfRequired.test.js} +51 -63
- package/test/{redux/middleware/createProgrammaticNavigationBlockerMiddleware.test.js → NavigationStack.blockProgrammaticNavigationIfRequired.test.js} +98 -78
- package/test/NavigationStack.general.test.js +68 -0
- package/test/NavigationStack.parseInputLocation.test.js +52 -0
- package/test/NavigationStack.removeBasePath.test.js +69 -0
- package/test/NavigationStack.test.js +97 -29
- package/test/data-storage/LocationDataStorage.test.js +3 -2
- package/test/index.js +7 -31
- package/test/index.test.js +4 -5
- package/test/parseQueryFromSearch.test.js +19 -0
- package/test/parseQueryString.test.js +18 -0
- package/test/scroll-position/ScrollPositionRestoration.test.js +34 -13
- package/test/scroll-position/createApp.js +8 -8
- package/test/scroll-position/withScrollableContainerAtIndexPageWithDisabledAutomaticScrollPositionRestoration.js +4 -4
- package/test/session/{InMemorySession.test.js → Session.InMemoryEnvironment.test.js} +10 -9
- package/test/session/{ServerSession.test.js → Session.ServerSideRenderEnvironment.test.js} +5 -4
- package/test/session/{WebBrowserSession.test.js → Session.WebBrowserEnvironment.test.js} +63 -13
- package/test/shouldWarn.js +44 -0
- package/test/stringifyQuery.test.js +65 -0
- package/types/index.d.ts +180 -34
- package/types/tsconfig.json +0 -1
- package/data-storage/package.json +0 -7
- package/lib/cjs/createSearchFromQuery.js +0 -13
- package/lib/cjs/debug.js +0 -12
- package/lib/cjs/redux/ActionTypes.js +0 -14
- package/lib/cjs/redux/ActionTypesInternal.js +0 -8
- package/lib/cjs/redux/Actions.js +0 -28
- package/lib/cjs/redux/createMiddlewares.js +0 -60
- package/lib/cjs/redux/index.js +0 -13
- package/lib/cjs/redux/internalLocationReducer.js +0 -14
- package/lib/cjs/redux/locationReducer.js +0 -13
- package/lib/cjs/redux/middleware/createAddInputLocationBasePathMiddleware.js +0 -32
- package/lib/cjs/redux/middleware/createNonProgrammaticNavigationBlockerMiddleware.js +0 -113
- package/lib/cjs/redux/middleware/createProgrammaticNavigationBlockerMiddleware.js +0 -94
- package/lib/cjs/redux/middleware/createRemoveOutputLocationBasePathMiddleware.js +0 -30
- package/lib/cjs/redux/middleware/createUpdateInternalLocationMiddleware.js +0 -73
- package/lib/cjs/redux/middleware/navigationOperationMiddleware.js +0 -40
- package/lib/cjs/redux/middleware/parseInputLocationMiddleware.js +0 -29
- package/lib/cjs/redux/middleware/updateLocationMiddleware.js +0 -34
- package/lib/cjs/session/InMemorySession.js +0 -22
- package/lib/cjs/session/WebBrowserSession.js +0 -20
- package/lib/data-storage/index.d.ts +0 -35
- package/lib/esm/createSearchFromQuery.js +0 -8
- package/lib/esm/debug.js +0 -7
- package/lib/esm/redux/ActionTypes.js +0 -9
- package/lib/esm/redux/ActionTypesInternal.js +0 -3
- package/lib/esm/redux/Actions.js +0 -22
- package/lib/esm/redux/createMiddlewares.js +0 -54
- package/lib/esm/redux/index.js +0 -4
- package/lib/esm/redux/internalLocationReducer.js +0 -8
- package/lib/esm/redux/locationReducer.js +0 -7
- package/lib/esm/redux/middleware/createAddInputLocationBasePathMiddleware.js +0 -27
- package/lib/esm/redux/middleware/createNonProgrammaticNavigationBlockerMiddleware.js +0 -108
- package/lib/esm/redux/middleware/createProgrammaticNavigationBlockerMiddleware.js +0 -88
- package/lib/esm/redux/middleware/createRemoveOutputLocationBasePathMiddleware.js +0 -25
- package/lib/esm/redux/middleware/createUpdateInternalLocationMiddleware.js +0 -68
- package/lib/esm/redux/middleware/navigationOperationMiddleware.js +0 -35
- package/lib/esm/redux/middleware/parseInputLocationMiddleware.js +0 -24
- package/lib/esm/redux/middleware/updateLocationMiddleware.js +0 -28
- package/lib/esm/session/InMemorySession.js +0 -15
- package/lib/esm/session/ServerSideRenderSession.js +0 -11
- package/lib/esm/session/WebBrowserSession.js +0 -13
- package/lib/redux/index.d.ts +0 -90
- package/lib/scroll-position/index.d.ts +0 -107
- package/redux/package.json +0 -7
- package/scroll-position/package.json +0 -7
- package/src/createSearchFromQuery.js +0 -9
- package/src/debug.js +0 -8
- package/src/redux/ActionTypes.js +0 -9
- package/src/redux/ActionTypesInternal.js +0 -3
- package/src/redux/Actions.js +0 -27
- package/src/redux/createMiddlewares.js +0 -65
- package/src/redux/index.js +0 -4
- package/src/redux/internalLocationReducer.js +0 -9
- package/src/redux/locationReducer.js +0 -8
- package/src/redux/middleware/createAddInputLocationBasePathMiddleware.js +0 -27
- package/src/redux/middleware/createNonProgrammaticNavigationBlockerMiddleware.js +0 -119
- package/src/redux/middleware/createProgrammaticNavigationBlockerMiddleware.js +0 -94
- package/src/redux/middleware/createRemoveOutputLocationBasePathMiddleware.js +0 -26
- package/src/redux/middleware/createUpdateInternalLocationMiddleware.js +0 -72
- package/src/redux/middleware/navigationOperationMiddleware.js +0 -34
- package/src/redux/middleware/parseInputLocationMiddleware.js +0 -23
- package/src/redux/middleware/updateLocationMiddleware.js +0 -28
- package/src/session/InMemorySession.js +0 -13
- package/src/session/ServerSideRenderSession.js +0 -9
- package/src/session/WebBrowserSession.js +0 -13
- package/test/middlewareTestUtil.js +0 -31
- package/test/redux/Action.test.js +0 -73
- package/test/redux/ActionTypes.test.js +0 -13
- package/test/redux/createMiddlewares.test.js +0 -96
- package/test/redux/index.test.js +0 -10
- package/test/redux/locationReducer.test.js +0 -39
- package/test/redux/middleware/createAddInputLocationBasePathMiddleware.test.js +0 -40
- package/test/redux/middleware/createRemoveOutputLocationBasePathMiddleware.test.js +0 -51
- package/test/redux/middleware/navigationOperationMiddleware.test.js +0 -78
- package/test/redux/middleware/parseInputLocationMiddleware.test.js +0 -62
- package/test/testUtil.js +0 -3
- package/types/data-storage/index.d.ts +0 -35
- package/types/redux/index.d.ts +0 -90
- package/types/scroll-position/index.d.ts +0 -107
- /package/lib/cjs/{session → environment}/lifecycle/InMemorySessionLifecycle.js +0 -0
- /package/lib/cjs/{session → environment}/lifecycle/WebBrowserSessionLifecycle.js +0 -0
- /package/lib/cjs/{session → environment}/lifecycle/page-lifecycle/PageLifecycle.js +0 -0
- /package/lib/cjs/{session → environment}/lifecycle/page-lifecycle/PageLifecycleInstance.js +0 -0
- /package/lib/cjs/{session → environment}/lifecycle/page-lifecycle/supportsConstructableEventTarget.js +0 -0
- /package/lib/cjs/{session → environment}/navigation/error/NavigationOutOfBoundsError.js +0 -0
- /package/lib/cjs/{session → environment}/navigation/operation/operations.js +0 -0
- /package/lib/esm/{session → environment}/lifecycle/InMemorySessionLifecycle.js +0 -0
- /package/lib/esm/{session → environment}/lifecycle/WebBrowserSessionLifecycle.js +0 -0
- /package/lib/esm/{session → environment}/lifecycle/page-lifecycle/PageLifecycle.js +0 -0
- /package/lib/esm/{session → environment}/lifecycle/page-lifecycle/PageLifecycleInstance.js +0 -0
- /package/lib/esm/{session → environment}/lifecycle/page-lifecycle/supportsConstructableEventTarget.js +0 -0
- /package/lib/esm/{session → environment}/navigation/error/NavigationOutOfBoundsError.js +0 -0
- /package/lib/esm/{session → environment}/navigation/operation/operations.js +0 -0
- /package/src/{session → environment}/lifecycle/InMemorySessionLifecycle.js +0 -0
- /package/src/{session → environment}/lifecycle/WebBrowserSessionLifecycle.js +0 -0
- /package/src/{session → environment}/lifecycle/page-lifecycle/PageLifecycle.js +0 -0
- /package/src/{session → environment}/lifecycle/page-lifecycle/PageLifecycleInstance.js +0 -0
- /package/src/{session → environment}/lifecycle/page-lifecycle/supportsConstructableEventTarget.js +0 -0
- /package/src/{session → environment}/navigation/error/NavigationOutOfBoundsError.js +0 -0
- /package/src/{session → environment}/navigation/operation/operations.js +0 -0
- /package/test/{parseInputLocationMiddleware.test.js → parseInputLocation.test.js} +0 -0
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import InMemoryEnvironment from './InMemoryEnvironment';
|
|
2
|
+
import ServerSideNavigation from './navigation/ServerSideNavigation';
|
|
3
|
+
|
|
4
|
+
// `ServerSideRenderSession` is just a `InMemorySession` that specifically prohibits any navigation.
|
|
5
|
+
export default class ServerSideRenderEnvironment extends InMemoryEnvironment {
|
|
6
|
+
constructor() {
|
|
7
|
+
super();
|
|
8
|
+
this.navigation = new ServerSideNavigation();
|
|
9
|
+
}
|
|
10
|
+
}
|
|
@@ -1,9 +1,15 @@
|
|
|
1
1
|
import WebBrowserDataStorage from './data-storage/WebBrowserDataStorage';
|
|
2
|
+
import WebBrowserSessionLifecycle from './lifecycle/WebBrowserSessionLifecycle';
|
|
3
|
+
import WebBrowserLog from './log/WebBrowserLog';
|
|
4
|
+
import WebBrowserNavigation from './navigation/WebBrowserNavigation';
|
|
2
5
|
import WebBrowserScrollPosition from './scroll-position/WebBrowserScrollPosition';
|
|
3
6
|
|
|
4
7
|
export default class WebBrowserEnvironment {
|
|
5
8
|
constructor() {
|
|
6
9
|
this.dataStorage = new WebBrowserDataStorage();
|
|
7
10
|
this.scrollPosition = new WebBrowserScrollPosition();
|
|
11
|
+
this.lifecycle = new WebBrowserSessionLifecycle();
|
|
12
|
+
this.log = new WebBrowserLog();
|
|
13
|
+
this.navigation = new WebBrowserNavigation();
|
|
8
14
|
}
|
|
9
15
|
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
const DEBUG_ENABLED = false;
|
|
2
|
+
|
|
3
|
+
export default class InMemoryLog {
|
|
4
|
+
debug(...args) {
|
|
5
|
+
if (DEBUG_ENABLED) {
|
|
6
|
+
// eslint-disable-next-line no-console
|
|
7
|
+
console.log(...args);
|
|
8
|
+
}
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
warn(...args) {
|
|
12
|
+
// eslint-disable-next-line no-console
|
|
13
|
+
console.warn(...args);
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
error(...args) {
|
|
17
|
+
// eslint-disable-next-line no-console
|
|
18
|
+
console.error(...args);
|
|
19
|
+
}
|
|
20
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
export default class WebBrowserLog {
|
|
2
|
+
debug(...args) {
|
|
3
|
+
if (window.NAVIGATION_STACK_DEBUG_ENABLED) {
|
|
4
|
+
// eslint-disable-next-line no-console
|
|
5
|
+
console.log(...args);
|
|
6
|
+
}
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
warn(...args) {
|
|
10
|
+
// eslint-disable-next-line no-console
|
|
11
|
+
console.warn(...args);
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
error(...args) {
|
|
15
|
+
// eslint-disable-next-line no-console
|
|
16
|
+
console.error(...args);
|
|
17
|
+
}
|
|
18
|
+
}
|
|
@@ -6,12 +6,23 @@ export default class InMemoryNavigation {
|
|
|
6
6
|
this._stack = [];
|
|
7
7
|
}
|
|
8
8
|
|
|
9
|
+
// Subscribes to any "asynchronous" changes of the current location,
|
|
10
|
+
// "asynchronous" changes being ones that happen out-of-sync with the code
|
|
11
|
+
// that might have potentially triggered those changes.
|
|
12
|
+
//
|
|
13
|
+
// For example, in a web browser, "Back"/"Forward" navigation happens out-of-sync
|
|
14
|
+
// with the code that calls `window.pushState()` or `window.replaceState()` function.
|
|
15
|
+
//
|
|
16
|
+
// Additionally, in a web browser, "Back"/"Forward" navigation could be triggered
|
|
17
|
+
// outside of the application code by user clicking those "Back"/"Forward" buttons manually
|
|
18
|
+
// in their web browser.
|
|
19
|
+
//
|
|
9
20
|
// eslint-disable-next-line no-unused-vars
|
|
10
|
-
|
|
11
|
-
// `InMemoryNavigation`
|
|
12
|
-
//
|
|
13
|
-
//
|
|
14
|
-
//
|
|
21
|
+
subscribeToAsyncrhonousLocationUpdates(listener) {
|
|
22
|
+
// `InMemoryNavigation` location changes are always "synchronous"
|
|
23
|
+
// with the code that initiated such changes, i.e. it always performs
|
|
24
|
+
// any navigation immediately at the time such navigation is triggered in code.
|
|
25
|
+
// Hence, this function doesn't have to "subscribe" to anything, so it's a "no op".
|
|
15
26
|
return () => {};
|
|
16
27
|
}
|
|
17
28
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/* eslint-disable no-underscore-dangle, max-classes-per-file */
|
|
2
2
|
|
|
3
|
-
import
|
|
3
|
+
import ServerSideRedirectError from './error/ServerSideRedirectError';
|
|
4
4
|
|
|
5
5
|
export default class ServerSideNavigation {
|
|
6
6
|
init(initialLocation, { operation, key, index, delta }) {
|
|
@@ -13,18 +13,27 @@ export default class ServerSideNavigation {
|
|
|
13
13
|
};
|
|
14
14
|
}
|
|
15
15
|
|
|
16
|
+
// Subscribes to any "asynchronous" changes of the current location,
|
|
17
|
+
// "asynchronous" changes being ones that happen out-of-sync with the code
|
|
18
|
+
// that might have potentially triggered those changes.
|
|
19
|
+
//
|
|
20
|
+
// For example, in a web browser, "Back"/"Forward" navigation happens out-of-sync
|
|
21
|
+
// with the code that calls `window.pushState()` or `window.replaceState()` function.
|
|
22
|
+
//
|
|
23
|
+
// Additionally, in a web browser, "Back"/"Forward" navigation could be triggered
|
|
24
|
+
// outside of the application code by user clicking those "Back"/"Forward" buttons manually
|
|
25
|
+
// in their web browser.
|
|
26
|
+
//
|
|
16
27
|
// eslint-disable-next-line no-unused-vars
|
|
17
|
-
|
|
18
|
-
// `ServerSideNavigation`
|
|
19
|
-
//
|
|
20
|
-
// So no asynchronous listener would ever be called
|
|
21
|
-
// due to no asynchronous events being dispatched.
|
|
28
|
+
subscribeToAsyncrhonousLocationUpdates(listener) {
|
|
29
|
+
// `ServerSideNavigation` location changes are prohibited, so they couldn't happen.
|
|
30
|
+
// Hence, this function doesn't have to "subscribe" to anything, so it's a "no op".
|
|
22
31
|
return () => {};
|
|
23
32
|
}
|
|
24
33
|
|
|
25
34
|
// eslint-disable-next-line no-unused-vars
|
|
26
35
|
navigate(location, { operation, key, index, delta }) {
|
|
27
|
-
throw new
|
|
36
|
+
throw new ServerSideRedirectError(location);
|
|
28
37
|
}
|
|
29
38
|
|
|
30
39
|
// eslint-disable-next-line no-unused-vars
|
|
@@ -48,15 +48,30 @@ export default class WebBrowserNavigation {
|
|
|
48
48
|
this._currentLocationIndex = NO_LOCATION_INDEX;
|
|
49
49
|
}
|
|
50
50
|
|
|
51
|
-
// Subscribes to changes
|
|
51
|
+
// Subscribes to any "asynchronous" changes of the current location,
|
|
52
|
+
// "asynchronous" changes being ones that happen out-of-sync with the code
|
|
53
|
+
// that might have potentially triggered those changes.
|
|
54
|
+
//
|
|
55
|
+
// For example, in a web browser, "Back"/"Forward" navigation happens out-of-sync
|
|
56
|
+
// with the code that calls `window.pushState()` or `window.replaceState()` function.
|
|
57
|
+
//
|
|
58
|
+
// Additionally, in a web browser, "Back"/"Forward" navigation could be triggered
|
|
59
|
+
// outside of the application code by user clicking those "Back"/"Forward" buttons manually
|
|
60
|
+
// in their web browser.
|
|
61
|
+
//
|
|
52
62
|
// Returns an `unsubscribe()` function which is "idempotent", i.e. it can be called multiple times.
|
|
53
|
-
|
|
63
|
+
//
|
|
64
|
+
subscribeToAsyncrhonousLocationUpdates(listener) {
|
|
54
65
|
const onPopState = () => {
|
|
55
66
|
// If "popstate" event is received before navigation is initialized,
|
|
56
|
-
// ignore such "popstate" event.
|
|
67
|
+
// ignore such "popstate" event. Such "ignore" behavior is logical from
|
|
68
|
+
// the application code's point of view: it doesn't expect any navigation events
|
|
69
|
+
// to be recorded before it has initialized the navigation.
|
|
57
70
|
// And besides, `this._currentLocationIndex` is not defined in such conditions.
|
|
58
71
|
if (this._currentLocationIndex === NO_LOCATION_INDEX) {
|
|
59
|
-
throw new Error(
|
|
72
|
+
throw new Error(
|
|
73
|
+
'Received a "popstate" event before finished initializing navigation',
|
|
74
|
+
);
|
|
60
75
|
}
|
|
61
76
|
const prevIndex = this._currentLocationIndex;
|
|
62
77
|
const { index } = this._getCurrentLocationState();
|
|
@@ -93,6 +108,15 @@ export default class WebBrowserNavigation {
|
|
|
93
108
|
};
|
|
94
109
|
}
|
|
95
110
|
|
|
111
|
+
// When run in a web browser, it could not only "start" a new navigation session
|
|
112
|
+
// but also "resume" a previously-started navigation session. That could happen
|
|
113
|
+
// when the user refreshes a page in a web browser which still retains
|
|
114
|
+
// the previous navigation session's data but at the same time restarts
|
|
115
|
+
// the javascript code from scratch.
|
|
116
|
+
//
|
|
117
|
+
// So this `init()` method handles both cases: when there's previous navigation session's data
|
|
118
|
+
// that should be restored and when there's no previous navigation session's data.
|
|
119
|
+
//
|
|
96
120
|
init(initialLocation, { operation, key, index, delta }) {
|
|
97
121
|
// Validate that `initialLocation` is same as `window.location`.
|
|
98
122
|
const isCurrentLocation =
|
|
@@ -109,7 +133,8 @@ export default class WebBrowserNavigation {
|
|
|
109
133
|
// by calling `window.history.replaceState()` on page load.
|
|
110
134
|
// Otherwise, `window.history.state` would be `null` for the initial location
|
|
111
135
|
// and there'd be no place to store the additional properties of the initial location
|
|
112
|
-
// such as `location.key`.
|
|
136
|
+
// such as `location.key`. Without `location.key` always being present the initial location object,
|
|
137
|
+
// there'd be a bug of incorrect scroll position being set on the initial location URL in some scenarios:
|
|
113
138
|
// https://github.com/taion/scroll-behavior/issues/215
|
|
114
139
|
//
|
|
115
140
|
// If the user opens the initial page for the first time, `window.history.state` will be `null`.
|
|
@@ -120,7 +145,7 @@ export default class WebBrowserNavigation {
|
|
|
120
145
|
// Create additional properties for the initial locaiton.
|
|
121
146
|
const additionalProperties = { key, index };
|
|
122
147
|
// Call `history.replaceState()`.
|
|
123
|
-
this.
|
|
148
|
+
this._navigateToLocationAndKeepItsAdditionalPropertiesInHistory(
|
|
124
149
|
initialLocation,
|
|
125
150
|
additionalProperties,
|
|
126
151
|
delta,
|
|
@@ -136,7 +161,7 @@ export default class WebBrowserNavigation {
|
|
|
136
161
|
navigate(location, { operation, key, index, delta }) {
|
|
137
162
|
const additionalProperties = { key, index };
|
|
138
163
|
|
|
139
|
-
this.
|
|
164
|
+
this._navigateToLocationAndKeepItsAdditionalPropertiesInHistory(
|
|
140
165
|
location,
|
|
141
166
|
additionalProperties,
|
|
142
167
|
delta,
|
|
@@ -205,7 +230,22 @@ export default class WebBrowserNavigation {
|
|
|
205
230
|
};
|
|
206
231
|
}
|
|
207
232
|
|
|
208
|
-
|
|
233
|
+
// Stores "additional" properties associated with `location` in web browser's history storage.
|
|
234
|
+
// Web browser's history storage is not intended for large datasets and should only be used
|
|
235
|
+
// to store small bits of data.
|
|
236
|
+
//
|
|
237
|
+
// "Some browsers save state objects to the user's disk so they can be restored after the user restarts
|
|
238
|
+
// the browser, and impose a size limit on the serialized representation of a state object, and will throw
|
|
239
|
+
// an exception if you pass a state object whose serialized representation is larger than that size limit.
|
|
240
|
+
// So in cases where you want to ensure you have more space than what some browsers might impose,
|
|
241
|
+
// you're encouraged to use sessionStorage and/or localStorage."
|
|
242
|
+
//
|
|
243
|
+
// Source: https://developer.mozilla.org/en-US/docs/Web/API/History/pushState
|
|
244
|
+
//
|
|
245
|
+
// To store large amounts of data, one could use `window.sessionStorage` instead.
|
|
246
|
+
// It is accessible via `DataStorage(session)` class.
|
|
247
|
+
//
|
|
248
|
+
_navigateToLocationAndKeepItsAdditionalPropertiesInHistory(
|
|
209
249
|
location,
|
|
210
250
|
additionalProperties,
|
|
211
251
|
delta,
|
|
@@ -61,10 +61,25 @@ export default class WebBrowserScrollPosition {
|
|
|
61
61
|
}
|
|
62
62
|
|
|
63
63
|
enableAutomaticScrollRestoration() {
|
|
64
|
+
// The default "auto" behavior seems to work in the following way:
|
|
65
|
+
//
|
|
66
|
+
// * It doesn't scroll to top on `window.history.pushState()` or `window.history.replaceState()`.
|
|
67
|
+
//
|
|
68
|
+
// * It does restore scroll position on "popstate" event
|
|
69
|
+
// (they say, in Firefox it happens before the event is dispatched,
|
|
70
|
+
// while in Chrome it happens after the event is dispatched)
|
|
71
|
+
//
|
|
72
|
+
// https://v5.reactrouter.com/web/guides/scroll-restoration
|
|
73
|
+
//
|
|
64
74
|
window.history.scrollRestoration = 'auto';
|
|
65
75
|
}
|
|
66
76
|
|
|
67
77
|
disableAutomaticScrollRestoration() {
|
|
78
|
+
// Setting `window.history.scrollRestoration` value updates it in the current history entry
|
|
79
|
+
// and any subsequent history entries.
|
|
80
|
+
// This means that it should be set at application initialization stage,
|
|
81
|
+
// that is before any navigation.
|
|
82
|
+
// https://majido.github.io/scroll-restoration-proposal/history-based-api.html
|
|
68
83
|
window.history.scrollRestoration = 'manual';
|
|
69
84
|
}
|
|
70
85
|
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
// Converts `Location` object to a `LocationBase` object.
|
|
2
|
+
// It hides properties of location such as `key` or `index`.
|
|
3
|
+
export default function getLocationBaseFromLocation(location) {
|
|
4
|
+
// eslint-disable-next-line no-unused-vars
|
|
5
|
+
const { key, index, ...locationBase } = location;
|
|
6
|
+
return locationBase;
|
|
7
|
+
}
|
package/src/getLocationUrl.js
CHANGED
|
@@ -1,11 +1,8 @@
|
|
|
1
|
-
import
|
|
1
|
+
import stringifyQueryAsSearch from './stringifyQueryAsSearch';
|
|
2
2
|
|
|
3
3
|
export default function getLocationUrl({ pathname, search, query, hash }) {
|
|
4
4
|
if (!search && query) {
|
|
5
|
-
|
|
6
|
-
if (queryString) {
|
|
7
|
-
search = `?${queryString}`;
|
|
8
|
-
}
|
|
5
|
+
search = stringifyQueryAsSearch(query);
|
|
9
6
|
}
|
|
10
7
|
|
|
11
8
|
return `${pathname}${search || ''}${hash || ''}`;
|
package/src/index.js
CHANGED
|
@@ -1,14 +1,11 @@
|
|
|
1
1
|
export { addBasePath, removeBasePath } from './basePath';
|
|
2
|
-
export addNavigationBlocker from './addNavigationBlocker';
|
|
3
|
-
export getLocationUrl from './getLocationUrl';
|
|
4
|
-
export parseLocationUrl from './parseLocationUrl';
|
|
5
|
-
export parseInputLocation from './parseInputLocation';
|
|
6
|
-
export NavigationStack from './NavigationStack';
|
|
7
|
-
export
|
|
8
|
-
export
|
|
9
|
-
export
|
|
10
|
-
export
|
|
11
|
-
export
|
|
12
|
-
export ServerSideRenderSession from './session/ServerSideRenderSession';
|
|
13
|
-
export ServerSideNavigationError from './session/navigation/error/ServerSideNavigationError';
|
|
14
|
-
export NavigationOutOfBoundsError from './session/navigation/error/NavigationOutOfBoundsError';
|
|
2
|
+
export { default as addNavigationBlocker } from './addNavigationBlocker';
|
|
3
|
+
export { default as getLocationUrl } from './getLocationUrl';
|
|
4
|
+
export { default as parseLocationUrl } from './parseLocationUrl';
|
|
5
|
+
export { default as parseInputLocation } from './parseInputLocation';
|
|
6
|
+
export { default as NavigationStack } from './NavigationStack';
|
|
7
|
+
export { default as InMemoryEnvironment } from './environment/InMemoryEnvironment';
|
|
8
|
+
export { default as WebBrowserEnvironment } from './environment/WebBrowserEnvironment';
|
|
9
|
+
export { default as ServerSideRenderEnvironment } from './environment/ServerSideRenderEnvironment';
|
|
10
|
+
export { default as ServerSideRedirectError } from './environment/navigation/error/ServerSideRedirectError';
|
|
11
|
+
export { default as NavigationOutOfBoundsError } from './environment/navigation/error/NavigationOutOfBoundsError';
|
|
@@ -1,30 +1,33 @@
|
|
|
1
1
|
/* eslint-disable no-underscore-dangle */
|
|
2
2
|
|
|
3
|
-
import debug from './debug';
|
|
4
3
|
import isPromise from './isPromise';
|
|
5
4
|
|
|
6
|
-
export function getNavigationBlockers(
|
|
7
|
-
return
|
|
5
|
+
export function getNavigationBlockers(container) {
|
|
6
|
+
return container._navigationBlockersList || [];
|
|
8
7
|
}
|
|
9
8
|
|
|
10
|
-
function addNavigationBlockerToTheList(blocker,
|
|
11
|
-
if (!
|
|
12
|
-
|
|
9
|
+
function addNavigationBlockerToTheList(blocker, container) {
|
|
10
|
+
if (!container._navigationBlockersList) {
|
|
11
|
+
container._navigationBlockersList = [];
|
|
13
12
|
}
|
|
14
|
-
|
|
13
|
+
container._navigationBlockersList.push(blocker);
|
|
15
14
|
}
|
|
16
15
|
|
|
17
|
-
function removeNavigationBlockerFromTheList(blocker,
|
|
18
|
-
if (
|
|
19
|
-
|
|
20
|
-
(_) => _ !== blocker
|
|
21
|
-
);
|
|
16
|
+
function removeNavigationBlockerFromTheList(blocker, container) {
|
|
17
|
+
if (container._navigationBlockersList) {
|
|
18
|
+
container._navigationBlockersList =
|
|
19
|
+
container._navigationBlockersList.filter((_) => _ !== blocker);
|
|
22
20
|
}
|
|
23
21
|
}
|
|
24
22
|
|
|
25
23
|
export function removeAllNavigationBlockers(session) {
|
|
24
|
+
// `navigationBlockers` are stored in `session`.
|
|
25
|
+
const container = session;
|
|
26
|
+
|
|
26
27
|
if (
|
|
27
|
-
getNavigationBlockers(
|
|
28
|
+
getNavigationBlockers(container).some(
|
|
29
|
+
(blocker) => blocker.beforeTermination,
|
|
30
|
+
)
|
|
28
31
|
) {
|
|
29
32
|
if (!session._removeTerminationBlocker) {
|
|
30
33
|
throw new Error(
|
|
@@ -34,33 +37,29 @@ export function removeAllNavigationBlockers(session) {
|
|
|
34
37
|
session._removeTerminationBlocker();
|
|
35
38
|
session._removeTerminationBlocker = undefined;
|
|
36
39
|
}
|
|
37
|
-
|
|
40
|
+
container._navigationBlockersList = [];
|
|
38
41
|
}
|
|
39
42
|
|
|
40
43
|
// Runs the `blocker` while ignoring any errors that might be thrown by it.
|
|
41
|
-
function runNavigationBlocker({ blocker }, location) {
|
|
44
|
+
function runNavigationBlocker({ blocker }, location, environment) {
|
|
42
45
|
let result;
|
|
43
46
|
try {
|
|
44
47
|
result = blocker(location);
|
|
45
48
|
} catch (error) {
|
|
46
|
-
|
|
47
|
-
console.warn(
|
|
49
|
+
environment.log.warn(
|
|
48
50
|
`Ignoring navigation blocker \`${blocker.name}\` that failed with \`${error}\`.`,
|
|
49
51
|
);
|
|
50
|
-
|
|
51
|
-
console.error(error);
|
|
52
|
+
environment.log.error(error);
|
|
52
53
|
}
|
|
53
54
|
|
|
54
55
|
// If the blocker returned a `Promise`, await for that `Promise`
|
|
55
56
|
// and then return the result.
|
|
56
57
|
if (isPromise(result)) {
|
|
57
58
|
return result.catch((error) => {
|
|
58
|
-
|
|
59
|
-
console.warn(
|
|
59
|
+
environment.log.warn(
|
|
60
60
|
`Ignoring navigation blocker \`${blocker.name}\` that failed with \`${error}\`.`,
|
|
61
61
|
);
|
|
62
|
-
|
|
63
|
-
console.error(error);
|
|
62
|
+
environment.log.error(error);
|
|
64
63
|
});
|
|
65
64
|
}
|
|
66
65
|
// The blocker didn't return a `Promise`.
|
|
@@ -71,23 +70,35 @@ function runNavigationBlocker({ blocker }, location) {
|
|
|
71
70
|
// Runs all blockers in order.
|
|
72
71
|
// If any blocker returns `true`, it stops and returns the result.
|
|
73
72
|
// If there's no such blocker, returns `undefined`.
|
|
74
|
-
export function runNavigationBlockers(
|
|
73
|
+
export function runNavigationBlockers(
|
|
74
|
+
navigationBlockers,
|
|
75
|
+
toLocation,
|
|
76
|
+
environment,
|
|
77
|
+
) {
|
|
75
78
|
if (navigationBlockers.length === 0) {
|
|
76
79
|
return undefined;
|
|
77
80
|
}
|
|
78
81
|
|
|
79
82
|
// Call the first blocker in the list.
|
|
80
|
-
const result = runNavigationBlocker(
|
|
83
|
+
const result = runNavigationBlocker(
|
|
84
|
+
navigationBlockers[0],
|
|
85
|
+
toLocation,
|
|
86
|
+
environment,
|
|
87
|
+
);
|
|
81
88
|
|
|
82
89
|
const next = () => {
|
|
83
90
|
// Proceed to the next blocker.
|
|
84
|
-
return runNavigationBlockers(
|
|
91
|
+
return runNavigationBlockers(
|
|
92
|
+
navigationBlockers.slice(1),
|
|
93
|
+
toLocation,
|
|
94
|
+
environment,
|
|
95
|
+
);
|
|
85
96
|
};
|
|
86
97
|
|
|
87
98
|
if (isPromise(result)) {
|
|
88
99
|
return result.then((resultValue) => {
|
|
89
100
|
if (resultValue) {
|
|
90
|
-
debug('Navigation blocked', toLocation.pathname);
|
|
101
|
+
environment.log.debug('Navigation blocked', toLocation.pathname);
|
|
91
102
|
return resultValue;
|
|
92
103
|
}
|
|
93
104
|
return next();
|
|
@@ -95,7 +106,7 @@ export function runNavigationBlockers(navigationBlockers, toLocation) {
|
|
|
95
106
|
}
|
|
96
107
|
|
|
97
108
|
if (result) {
|
|
98
|
-
debug('Navigation blocked', toLocation.pathname);
|
|
109
|
+
environment.log.debug('Navigation blocked', toLocation.pathname);
|
|
99
110
|
return result;
|
|
100
111
|
}
|
|
101
112
|
return next();
|
|
@@ -103,7 +114,14 @@ export function runNavigationBlockers(navigationBlockers, toLocation) {
|
|
|
103
114
|
|
|
104
115
|
/* istanbul ignore next: not testable with Karma */
|
|
105
116
|
function terminationBlocker(session) {
|
|
106
|
-
|
|
117
|
+
// `navigationBlockers` are stored in `session`.
|
|
118
|
+
const container = session;
|
|
119
|
+
|
|
120
|
+
const result = runNavigationBlockers(
|
|
121
|
+
getNavigationBlockers(container),
|
|
122
|
+
null,
|
|
123
|
+
session.environment,
|
|
124
|
+
);
|
|
107
125
|
|
|
108
126
|
// If no blocker returned anything, so don't prevent the navigation.
|
|
109
127
|
if (!result) {
|
|
@@ -123,6 +141,9 @@ function terminationBlocker(session) {
|
|
|
123
141
|
}
|
|
124
142
|
|
|
125
143
|
export function addNavigationBlocker(session, blocker) {
|
|
144
|
+
// `navigationBlockers` are stored in `session`.
|
|
145
|
+
const container = session;
|
|
146
|
+
|
|
126
147
|
// All navigation blockers also run on `beforeTermination` event.
|
|
127
148
|
// If required, this could be a parameter of this function.
|
|
128
149
|
// The rationale could be that adding a `beforeunload` listener
|
|
@@ -141,7 +162,7 @@ export function addNavigationBlocker(session, blocker) {
|
|
|
141
162
|
// https://developer.mozilla.org/en-US/docs/Web/API/Window/beforeunload_event
|
|
142
163
|
if (
|
|
143
164
|
beforeTermination &&
|
|
144
|
-
!getNavigationBlockers(
|
|
165
|
+
!getNavigationBlockers(container).some(
|
|
145
166
|
(navigationBlocker) => navigationBlocker.beforeTermination,
|
|
146
167
|
)
|
|
147
168
|
) {
|
|
@@ -151,21 +172,21 @@ export function addNavigationBlocker(session, blocker) {
|
|
|
151
172
|
);
|
|
152
173
|
}
|
|
153
174
|
session._removeTerminationBlocker =
|
|
154
|
-
session.lifecycle.addTerminationBlocker(() => {
|
|
175
|
+
session.environment.lifecycle.addTerminationBlocker(() => {
|
|
155
176
|
return terminationBlocker(session);
|
|
156
177
|
});
|
|
157
178
|
}
|
|
158
179
|
|
|
159
180
|
const newNavigationBlocker = { blocker, beforeTermination };
|
|
160
|
-
addNavigationBlockerToTheList(newNavigationBlocker,
|
|
181
|
+
addNavigationBlockerToTheList(newNavigationBlocker, container);
|
|
161
182
|
|
|
162
183
|
return () => {
|
|
163
|
-
removeNavigationBlockerFromTheList(newNavigationBlocker,
|
|
184
|
+
removeNavigationBlockerFromTheList(newNavigationBlocker, container);
|
|
164
185
|
|
|
165
186
|
// If it was the last "beforeTermination" blocker, remove navigation blocker.
|
|
166
187
|
if (
|
|
167
188
|
beforeTermination &&
|
|
168
|
-
!getNavigationBlockers(
|
|
189
|
+
!getNavigationBlockers(container).some(
|
|
169
190
|
(navigationBlocker) => navigationBlocker.beforeTermination,
|
|
170
191
|
)
|
|
171
192
|
) {
|