navigation-stack 0.3.1 → 0.4.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/README.md +603 -163
- package/data-storage/package.json +6 -0
- package/karma.conf.cjs +21 -4
- package/lib/cjs/NavigationStack.js +73 -0
- package/lib/cjs/data-storage/DataStorage.js +71 -0
- package/lib/cjs/data-storage/LocationDataStorage.js +29 -0
- package/lib/cjs/data-storage/index.js +9 -0
- package/lib/cjs/environment/InMemoryEnvironment.js +15 -0
- package/lib/cjs/environment/WebBrowserEnvironment.js +15 -0
- package/lib/cjs/environment/data-storage/InMemoryDataStorage.js +27 -0
- package/lib/cjs/environment/data-storage/WebBrowserDataStorage.js +21 -0
- package/lib/cjs/environment/scroll-position/InMemoryScrollPosition.js +44 -0
- package/lib/cjs/environment/scroll-position/WebBrowserScrollPosition.js +60 -0
- package/lib/cjs/getLocationFromInternalLocation.js +14 -0
- package/lib/cjs/index.js +20 -16
- package/lib/cjs/navigationBlockers.js +25 -23
- package/lib/cjs/{normalizeInputLocation.js → parseInputLocation.js} +25 -9
- package/lib/cjs/{ActionTypes.js → redux/ActionTypes.js} +1 -1
- package/lib/cjs/redux/ActionTypesInternal.js +8 -0
- package/lib/cjs/{Actions.js → redux/Actions.js} +5 -4
- package/lib/cjs/redux/createMiddlewares.js +60 -0
- package/lib/cjs/redux/index.js +13 -0
- package/lib/cjs/redux/internalLocationReducer.js +14 -0
- package/lib/cjs/redux/middleware/createAddInputLocationBasePathMiddleware.js +32 -0
- package/lib/cjs/redux/middleware/createNonProgrammaticNavigationBlockerMiddleware.js +113 -0
- package/lib/cjs/redux/middleware/createProgrammaticNavigationBlockerMiddleware.js +94 -0
- package/lib/cjs/redux/middleware/createRemoveOutputLocationBasePathMiddleware.js +30 -0
- package/lib/cjs/redux/middleware/createUpdateInternalLocationMiddleware.js +73 -0
- package/lib/cjs/{middleware/navigationActionMiddleware.js → redux/middleware/navigationOperationMiddleware.js} +11 -8
- package/lib/cjs/{middleware/normalizeInputLocationMiddleware.js → redux/middleware/parseInputLocationMiddleware.js} +6 -4
- package/lib/cjs/redux/middleware/updateLocationMiddleware.js +34 -0
- package/lib/cjs/scroll-position/PageScrollPositionSetter.js +97 -0
- package/lib/cjs/scroll-position/ScrollPositionAutoSaver.js +130 -0
- package/lib/cjs/scroll-position/ScrollPositionRestoration.js +383 -0
- package/lib/cjs/scroll-position/ScrollPositionSaver.js +81 -0
- package/lib/cjs/scroll-position/ScrollPositionSetter.js +16 -0
- package/lib/cjs/scroll-position/constants.js +5 -0
- package/lib/cjs/scroll-position/index.js +7 -0
- package/lib/cjs/scroll-position/scheduleNextTick.js +11 -0
- package/lib/cjs/session/InMemorySession.js +22 -0
- package/lib/cjs/session/ServerSideRenderSession.js +17 -0
- package/lib/cjs/session/Session.js +196 -0
- package/lib/cjs/session/WebBrowserSession.js +20 -0
- package/lib/cjs/session/key/createSessionKey.js +23 -0
- package/lib/cjs/session/lifecycle/InMemorySessionLifecycle.js +19 -0
- package/lib/cjs/session/lifecycle/WebBrowserSessionLifecycle.js +128 -0
- package/lib/cjs/session/lifecycle/page-lifecycle/PageLifecycle.js +269 -0
- package/lib/cjs/session/lifecycle/page-lifecycle/PageLifecycleInstance.js +8 -0
- package/lib/cjs/session/lifecycle/page-lifecycle/supportsConstructableEventTarget.js +33 -0
- package/lib/cjs/session/navigation/InMemoryNavigation.js +104 -0
- package/lib/cjs/session/navigation/ServerSideNavigation.js +61 -0
- package/lib/cjs/session/navigation/WebBrowserNavigation.js +221 -0
- package/lib/cjs/session/navigation/error/NavigationOutOfBoundsError.js +12 -0
- package/lib/cjs/session/navigation/error/ServerSideNavigationError.js +21 -0
- package/lib/cjs/session/navigation/operation/operations.js +11 -0
- package/lib/cjs/session/subscription/Subscription.js +81 -0
- package/lib/data-storage/index.d.ts +35 -0
- package/lib/esm/NavigationStack.js +66 -0
- package/lib/esm/data-storage/DataStorage.js +65 -0
- package/lib/esm/data-storage/LocationDataStorage.js +22 -0
- package/lib/esm/data-storage/index.js +2 -0
- package/lib/esm/environment/InMemoryEnvironment.js +8 -0
- package/lib/esm/environment/WebBrowserEnvironment.js +8 -0
- package/lib/esm/environment/data-storage/InMemoryDataStorage.js +21 -0
- package/lib/esm/environment/data-storage/WebBrowserDataStorage.js +15 -0
- package/lib/esm/environment/scroll-position/InMemoryScrollPosition.js +38 -0
- package/lib/esm/environment/scroll-position/WebBrowserScrollPosition.js +54 -0
- package/lib/esm/getLocationFromInternalLocation.js +9 -0
- package/lib/esm/index.js +10 -8
- package/lib/esm/navigationBlockers.js +25 -23
- package/lib/esm/{normalizeInputLocation.js → parseInputLocation.js} +24 -8
- package/lib/esm/{ActionTypes.js → redux/ActionTypes.js} +1 -1
- package/lib/esm/redux/ActionTypesInternal.js +3 -0
- package/lib/esm/{Actions.js → redux/Actions.js} +5 -4
- package/lib/esm/redux/createMiddlewares.js +54 -0
- package/lib/esm/redux/index.js +4 -0
- package/lib/esm/redux/internalLocationReducer.js +8 -0
- package/lib/esm/redux/middleware/createAddInputLocationBasePathMiddleware.js +27 -0
- package/lib/esm/redux/middleware/createNonProgrammaticNavigationBlockerMiddleware.js +108 -0
- package/lib/esm/redux/middleware/createProgrammaticNavigationBlockerMiddleware.js +88 -0
- package/lib/esm/redux/middleware/createRemoveOutputLocationBasePathMiddleware.js +25 -0
- package/lib/esm/redux/middleware/createUpdateInternalLocationMiddleware.js +68 -0
- package/lib/esm/{middleware/navigationActionMiddleware.js → redux/middleware/navigationOperationMiddleware.js} +10 -7
- package/lib/esm/{middleware/normalizeInputLocationMiddleware.js → redux/middleware/parseInputLocationMiddleware.js} +5 -3
- package/lib/esm/redux/middleware/updateLocationMiddleware.js +28 -0
- package/lib/esm/scroll-position/PageScrollPositionSetter.js +91 -0
- package/lib/esm/scroll-position/ScrollPositionAutoSaver.js +123 -0
- package/lib/esm/scroll-position/ScrollPositionRestoration.js +376 -0
- package/lib/esm/scroll-position/ScrollPositionSaver.js +74 -0
- package/lib/esm/scroll-position/ScrollPositionSetter.js +10 -0
- package/lib/esm/scroll-position/constants.js +1 -0
- package/lib/esm/scroll-position/index.js +1 -0
- package/lib/esm/scroll-position/scheduleNextTick.js +6 -0
- package/lib/esm/session/InMemorySession.js +15 -0
- package/lib/esm/session/ServerSideRenderSession.js +11 -0
- package/lib/esm/session/Session.js +189 -0
- package/lib/esm/session/WebBrowserSession.js +13 -0
- package/lib/esm/session/key/createSessionKey.js +18 -0
- package/lib/esm/session/lifecycle/InMemorySessionLifecycle.js +13 -0
- package/lib/esm/session/lifecycle/WebBrowserSessionLifecycle.js +120 -0
- package/lib/esm/session/lifecycle/page-lifecycle/PageLifecycle.js +263 -0
- package/lib/esm/session/lifecycle/page-lifecycle/PageLifecycleInstance.js +2 -0
- package/lib/esm/session/lifecycle/page-lifecycle/supportsConstructableEventTarget.js +30 -0
- package/lib/esm/session/navigation/InMemoryNavigation.js +97 -0
- package/lib/esm/session/navigation/ServerSideNavigation.js +54 -0
- package/lib/esm/session/navigation/WebBrowserNavigation.js +213 -0
- package/lib/esm/session/navigation/error/NavigationOutOfBoundsError.js +6 -0
- package/lib/esm/session/navigation/error/ServerSideNavigationError.js +14 -0
- package/lib/esm/session/navigation/operation/operations.js +6 -0
- package/lib/esm/session/subscription/Subscription.js +75 -0
- package/lib/index.d.ts +178 -157
- package/lib/redux/index.d.ts +90 -0
- package/lib/scroll-position/index.d.ts +107 -0
- package/package.json +9 -5
- package/redux/package.json +6 -0
- package/scroll-position/package.json +6 -0
- package/src/NavigationStack.js +84 -0
- package/src/data-storage/DataStorage.js +69 -0
- package/src/data-storage/LocationDataStorage.js +23 -0
- package/src/data-storage/index.js +2 -0
- package/src/environment/InMemoryEnvironment.js +9 -0
- package/src/environment/WebBrowserEnvironment.js +9 -0
- package/src/environment/data-storage/InMemoryDataStorage.js +23 -0
- package/src/environment/data-storage/WebBrowserDataStorage.js +17 -0
- package/src/environment/scroll-position/InMemoryScrollPosition.js +45 -0
- package/src/environment/scroll-position/WebBrowserScrollPosition.js +72 -0
- package/src/getLocationFromInternalLocation.js +7 -0
- package/src/index.js +10 -8
- package/src/navigationBlockers.js +28 -27
- package/src/{normalizeInputLocation.js → parseInputLocation.js} +23 -8
- package/src/{ActionTypes.js → redux/ActionTypes.js} +1 -1
- package/src/redux/ActionTypesInternal.js +3 -0
- package/src/{Actions.js → redux/Actions.js} +4 -3
- package/src/redux/createMiddlewares.js +65 -0
- package/src/redux/index.js +4 -0
- package/src/redux/internalLocationReducer.js +9 -0
- package/src/redux/middleware/createAddInputLocationBasePathMiddleware.js +27 -0
- package/src/redux/middleware/createNonProgrammaticNavigationBlockerMiddleware.js +119 -0
- package/src/redux/middleware/createProgrammaticNavigationBlockerMiddleware.js +94 -0
- package/src/redux/middleware/createRemoveOutputLocationBasePathMiddleware.js +26 -0
- package/src/redux/middleware/createUpdateInternalLocationMiddleware.js +72 -0
- package/src/{middleware/navigationActionMiddleware.js → redux/middleware/navigationOperationMiddleware.js} +10 -3
- package/src/{middleware/normalizeInputLocationMiddleware.js → redux/middleware/parseInputLocationMiddleware.js} +5 -3
- package/src/redux/middleware/updateLocationMiddleware.js +28 -0
- package/src/scroll-position/PageScrollPositionSetter.js +110 -0
- package/src/scroll-position/ScrollPositionAutoSaver.js +151 -0
- package/src/scroll-position/ScrollPositionRestoration.js +506 -0
- package/src/scroll-position/ScrollPositionSaver.js +100 -0
- package/src/scroll-position/ScrollPositionSetter.js +16 -0
- package/src/scroll-position/constants.js +1 -0
- package/src/scroll-position/index.js +1 -0
- package/src/scroll-position/scheduleNextTick.js +6 -0
- package/src/session/InMemorySession.js +13 -0
- package/src/session/ServerSideRenderSession.js +9 -0
- package/src/session/Session.js +216 -0
- package/src/session/WebBrowserSession.js +13 -0
- package/src/session/key/createSessionKey.js +18 -0
- package/src/session/lifecycle/InMemorySessionLifecycle.js +13 -0
- package/src/session/lifecycle/WebBrowserSessionLifecycle.js +126 -0
- package/src/session/lifecycle/page-lifecycle/PageLifecycle.js +291 -0
- package/src/session/lifecycle/page-lifecycle/PageLifecycleInstance.js +3 -0
- package/src/session/lifecycle/page-lifecycle/supportsConstructableEventTarget.js +32 -0
- package/src/session/navigation/InMemoryNavigation.js +78 -0
- package/src/session/navigation/ServerSideNavigation.js +43 -0
- package/src/session/navigation/WebBrowserNavigation.js +224 -0
- package/src/session/navigation/error/NavigationOutOfBoundsError.js +7 -0
- package/src/session/navigation/error/ServerSideNavigationError.js +18 -0
- package/src/session/navigation/operation/operations.js +6 -0
- package/src/session/subscription/Subscription.js +76 -0
- package/test/NavigationStack.test.js +296 -0
- package/test/{LocationDataStorage.test.js → data-storage/LocationDataStorage.test.js} +3 -3
- package/test/data-storage/index.test.js +8 -0
- package/test/index.js +12 -0
- package/test/index.test.js +8 -7
- package/test/{helpers.js → middlewareTestUtil.js} +9 -12
- package/test/{normalizeInputLocation.test.js → parseInputLocationMiddleware.test.js} +9 -9
- package/test/{Action.test.js → redux/Action.test.js} +7 -6
- package/test/{ActionTypes.test.js → redux/ActionTypes.test.js} +2 -2
- package/test/redux/createMiddlewares.test.js +96 -0
- package/test/redux/index.test.js +10 -0
- package/test/{locationReducer.test.js → redux/locationReducer.test.js} +4 -7
- package/test/redux/middleware/createAddInputLocationBasePathMiddleware.test.js +40 -0
- package/test/redux/middleware/createNonProgrammaticNavigationBlockerMiddleware.test.js +264 -0
- package/test/redux/middleware/createProgrammaticNavigationBlockerMiddleware.test.js +312 -0
- package/test/redux/middleware/createRemoveOutputLocationBasePathMiddleware.test.js +51 -0
- package/test/{middleware/navigationActionMiddleware.test.js → redux/middleware/navigationOperationMiddleware.test.js} +16 -12
- package/test/{middleware/normalizeInputLocationMiddleware.test.js → redux/middleware/parseInputLocationMiddleware.test.js} +4 -4
- package/test/scroll-position/ScrollPositionRestoration.test.js +418 -0
- package/test/scroll-position/addScrollableContainer.js +36 -0
- package/test/scroll-position/addScrollableContainerWithHyperlink.js +50 -0
- package/test/scroll-position/createApp.js +112 -0
- package/test/scroll-position/delay.js +9 -0
- package/test/scroll-position/mockPageLifecycle.js +17 -0
- package/test/scroll-position/runApp.js +24 -0
- package/test/scroll-position/withScrollableContainerAtIndexPage.js +62 -0
- package/test/session/InMemorySession.test.js +348 -0
- package/test/session/ServerSession.test.js +17 -9
- package/test/session/WebBrowserSession.test.js +265 -0
- package/test/testUtil.js +3 -0
- package/types/data-storage/index.d.ts +35 -0
- package/types/index.d.ts +178 -157
- package/types/redux/index.d.ts +90 -0
- package/types/scroll-position/index.d.ts +107 -0
- package/types/tsconfig.json +1 -1
- package/lib/cjs/LocationDataStorage.js +0 -61
- package/lib/cjs/addBeforeLocationChangeListener.js +0 -7
- package/lib/cjs/beforeLocationChangeListeners.js +0 -51
- package/lib/cjs/createMiddlewares.js +0 -47
- package/lib/cjs/middleware/createBasePathMiddleware.js +0 -24
- package/lib/cjs/middleware/createBeforeLocationChangeListenerMiddleware.js +0 -39
- package/lib/cjs/middleware/createLocationMiddleware.js +0 -56
- package/lib/cjs/middleware/createNavigationBlockerMiddleware.js +0 -161
- package/lib/cjs/middleware/createTransformLocationMiddleware.js +0 -38
- package/lib/cjs/onlyAllowedOnClientSide.js +0 -10
- package/lib/cjs/session/BrowserSession.js +0 -235
- package/lib/cjs/session/MemorySession.js +0 -223
- package/lib/cjs/session/ServerSession.js +0 -65
- package/lib/esm/LocationDataStorage.js +0 -54
- package/lib/esm/addBeforeLocationChangeListener.js +0 -2
- package/lib/esm/beforeLocationChangeListeners.js +0 -44
- package/lib/esm/createMiddlewares.js +0 -41
- package/lib/esm/middleware/createBasePathMiddleware.js +0 -19
- package/lib/esm/middleware/createBeforeLocationChangeListenerMiddleware.js +0 -34
- package/lib/esm/middleware/createLocationMiddleware.js +0 -50
- package/lib/esm/middleware/createNavigationBlockerMiddleware.js +0 -156
- package/lib/esm/middleware/createTransformLocationMiddleware.js +0 -33
- package/lib/esm/onlyAllowedOnClientSide.js +0 -5
- package/lib/esm/session/BrowserSession.js +0 -229
- package/lib/esm/session/MemorySession.js +0 -217
- package/lib/esm/session/ServerSession.js +0 -58
- package/src/LocationDataStorage.js +0 -60
- package/src/addBeforeLocationChangeListener.js +0 -2
- package/src/beforeLocationChangeListeners.js +0 -54
- package/src/createMiddlewares.js +0 -45
- package/src/middleware/createBasePathMiddleware.js +0 -20
- package/src/middleware/createBeforeLocationChangeListenerMiddleware.js +0 -40
- package/src/middleware/createLocationMiddleware.js +0 -55
- package/src/middleware/createNavigationBlockerMiddleware.js +0 -168
- package/src/middleware/createTransformLocationMiddleware.js +0 -29
- package/src/onlyAllowedOnClientSide.js +0 -5
- package/src/session/BrowserSession.js +0 -235
- package/src/session/MemorySession.js +0 -219
- package/src/session/ServerSession.js +0 -67
- package/test/createMiddlewares.test.js +0 -62
- package/test/middleware/createBasePathMiddleware.test.js +0 -67
- package/test/middleware/createBeforeLocationChangeListenerMiddleware.test.js +0 -141
- package/test/middleware/createNavigationBlockerMiddleware.test.js +0 -471
- package/test/middleware/createTransformLocationMiddleware.test.js +0 -44
- package/test/session/BrowserSession.test.js +0 -182
- package/test/session/MemorySession.test.js +0 -244
- /package/lib/cjs/{locationReducer.js → redux/locationReducer.js} +0 -0
- /package/lib/esm/{locationReducer.js → redux/locationReducer.js} +0 -0
- /package/src/{locationReducer.js → redux/locationReducer.js} +0 -0
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import ActionTypes from './ActionTypes';
|
|
2
2
|
export default {
|
|
3
|
-
init:
|
|
4
|
-
type: ActionTypes.INIT
|
|
3
|
+
init: initialLocation => ({
|
|
4
|
+
type: ActionTypes.INIT,
|
|
5
|
+
payload: initialLocation
|
|
5
6
|
}),
|
|
6
7
|
push: location => ({
|
|
7
8
|
type: ActionTypes.PUSH,
|
|
@@ -15,7 +16,7 @@ export default {
|
|
|
15
16
|
type: ActionTypes.SHIFT,
|
|
16
17
|
payload: delta
|
|
17
18
|
}),
|
|
18
|
-
|
|
19
|
-
type: ActionTypes.
|
|
19
|
+
stop: () => ({
|
|
20
|
+
type: ActionTypes.STOP
|
|
20
21
|
})
|
|
21
22
|
};
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import createAddInputLocationBasePathMiddleware from './middleware/createAddInputLocationBasePathMiddleware';
|
|
2
|
+
import createNonProgrammaticNavigationBlockerMiddleware from './middleware/createNonProgrammaticNavigationBlockerMiddleware';
|
|
3
|
+
import createProgrammaticNavigationBlockerMiddleware from './middleware/createProgrammaticNavigationBlockerMiddleware';
|
|
4
|
+
import createRemoveOutputLocationBasePathMiddleware from './middleware/createRemoveOutputLocationBasePathMiddleware';
|
|
5
|
+
import createUpdateInternalLocationMiddleware from './middleware/createUpdateInternalLocationMiddleware';
|
|
6
|
+
import navigationActionMiddleware from './middleware/navigationOperationMiddleware';
|
|
7
|
+
import parseInputLocationMiddleware from './middleware/parseInputLocationMiddleware';
|
|
8
|
+
import updateLocationMiddleware from './middleware/updateLocationMiddleware';
|
|
9
|
+
export default function createMiddlewares(session, options) {
|
|
10
|
+
// Allows temporarily ignoring location update events.
|
|
11
|
+
let shouldIgnoreNavigationLocationSubscriptionEvents = false;
|
|
12
|
+
const ignoreNavigationLocationSubscriptionEvents = func => {
|
|
13
|
+
shouldIgnoreNavigationLocationSubscriptionEvents = true;
|
|
14
|
+
func();
|
|
15
|
+
shouldIgnoreNavigationLocationSubscriptionEvents = false;
|
|
16
|
+
};
|
|
17
|
+
const middlewares = [
|
|
18
|
+
// Validates that the action "payload" (input location) is a proper `NormalizedInputLocation`.
|
|
19
|
+
parseInputLocationMiddleware,
|
|
20
|
+
// Transforms a "PUSH" / "REPLACE" action into a "NAVIGATE" action.
|
|
21
|
+
navigationActionMiddleware,
|
|
22
|
+
// If a website is hosted under a certain path (`basePath`)
|
|
23
|
+
// then this middleware will automatically hide that starting segment from the `pathname` of `location`s.
|
|
24
|
+
createAddInputLocationBasePathMiddleware(options && options.basePath),
|
|
25
|
+
// Allows blocking navigation.
|
|
26
|
+
// Handles `NAVIGATE` actions dispatched by the application itself.
|
|
27
|
+
createProgrammaticNavigationBlockerMiddleware(session),
|
|
28
|
+
// This "middleware" performs the actual navigation according to the `session` being used.
|
|
29
|
+
// For example, when `WebBrowserSession` is used, it calls methods of the `history` object.
|
|
30
|
+
createUpdateInternalLocationMiddleware(session, {
|
|
31
|
+
shouldIgnoreNavigationLocationSubscriptionEvents: () => shouldIgnoreNavigationLocationSubscriptionEvents
|
|
32
|
+
}),
|
|
33
|
+
// If a website is hosted under a certain path (`basePath`)
|
|
34
|
+
// then this middleware will automatically hide that starting segment from the `pathname` of `location`s.
|
|
35
|
+
createRemoveOutputLocationBasePathMiddleware(options && options.basePath),
|
|
36
|
+
// Allows blocking navigation.
|
|
37
|
+
// Handles location `UPDATE` actions dispatched in response to location update events.
|
|
38
|
+
createNonProgrammaticNavigationBlockerMiddleware(session, {
|
|
39
|
+
ignoreNavigationLocationSubscriptionEvents
|
|
40
|
+
})];
|
|
41
|
+
|
|
42
|
+
// Add `updateLocationMiddleware()`.
|
|
43
|
+
// It dispatches an `UPDATE` action with the new location
|
|
44
|
+
// so that the reducer could update it in global state.
|
|
45
|
+
//
|
|
46
|
+
// If `_internalLocationReducer` option is passed,
|
|
47
|
+
// it will not add this middleware. This is only used in tests.
|
|
48
|
+
//
|
|
49
|
+
// eslint-disable-next-line no-underscore-dangle
|
|
50
|
+
if (!(options && options._internalLocationReducer)) {
|
|
51
|
+
middlewares.push(updateLocationMiddleware);
|
|
52
|
+
}
|
|
53
|
+
return middlewares;
|
|
54
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import ActionTypesInternal from './ActionTypesInternal';
|
|
2
|
+
export default function internalLocationReducer(state, action) {
|
|
3
|
+
// eslint-disable-next-line no-underscore-dangle
|
|
4
|
+
if (action.type === ActionTypesInternal.INTERNAL_LOCATION_UPDATE) {
|
|
5
|
+
return action.payload;
|
|
6
|
+
}
|
|
7
|
+
return state;
|
|
8
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { addBasePath } from '../../basePath';
|
|
2
|
+
import ActionTypes from '../ActionTypes';
|
|
3
|
+
|
|
4
|
+
// Creates a "middleware" that, when a website is hosted under a certain path (`basePath`),
|
|
5
|
+
// automatically hides that starting segment from the `pathname` of `location`s.
|
|
6
|
+
export default function createAddInputLocationBasePathMiddleware(basePath) {
|
|
7
|
+
return function addInputLocationBasePathMiddleware() {
|
|
8
|
+
return next => action => {
|
|
9
|
+
const {
|
|
10
|
+
type,
|
|
11
|
+
payload
|
|
12
|
+
} = action;
|
|
13
|
+
switch (type) {
|
|
14
|
+
// Transforms `NAVIGATE` action payload (`location`).
|
|
15
|
+
case ActionTypes.NAVIGATE:
|
|
16
|
+
return next({
|
|
17
|
+
type,
|
|
18
|
+
payload: Object.assign({}, payload, {
|
|
19
|
+
location: addBasePath(payload.location, basePath)
|
|
20
|
+
})
|
|
21
|
+
});
|
|
22
|
+
default:
|
|
23
|
+
return next(action);
|
|
24
|
+
}
|
|
25
|
+
};
|
|
26
|
+
};
|
|
27
|
+
}
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
import getLocationFromInternalLocation from '../../getLocationFromInternalLocation';
|
|
2
|
+
import isPromise from '../../isPromise';
|
|
3
|
+
import { getNavigationBlockers, runNavigationBlockers } from '../../navigationBlockers';
|
|
4
|
+
import NavigationOperations from '../../session/navigation/operation/operations';
|
|
5
|
+
import ActionTypesInternal from '../ActionTypesInternal';
|
|
6
|
+
import { createNavigationBlockersEvaluationStatus } from './createProgrammaticNavigationBlockerMiddleware';
|
|
7
|
+
|
|
8
|
+
// Creates a "middleware" that applies navigation blockers for non-programmatic navigation,
|
|
9
|
+
// i.e. the navigation that wasn't triggered by the application code and was triggered, say, by the user.
|
|
10
|
+
// It also handles programmatic `SHIFT` navigation because it doesn't know the new location until
|
|
11
|
+
// the environment performs the `delta` navigation and then tells the new location to `navigation-stack`.
|
|
12
|
+
export default function createNonProgrammaticNavigationBlockerMiddleware(session, {
|
|
13
|
+
ignoreNavigationLocationSubscriptionEvents
|
|
14
|
+
}) {
|
|
15
|
+
return function nonProgrammaticNavigationBlockerMiddleware() {
|
|
16
|
+
return next => action => {
|
|
17
|
+
const {
|
|
18
|
+
type,
|
|
19
|
+
payload
|
|
20
|
+
} = action;
|
|
21
|
+
|
|
22
|
+
// Declaring `result` variable here fixes ESLint error:
|
|
23
|
+
// "Unexpected lexical declaration in case block".
|
|
24
|
+
let result;
|
|
25
|
+
switch (type) {
|
|
26
|
+
// One could ask: Why run navigation blockers on `UPDATE` Redux action?
|
|
27
|
+
// Why not just run navigation blockers on `NAVIGATE` and `SHIFT` Redux action?
|
|
28
|
+
// The reason why it handles `UPDATE` Redux actions here is because
|
|
29
|
+
// `NAVIGATE` Redux actions are only emitted for programmatic "push" or "replace" navigation
|
|
30
|
+
// initiated by the application code, and there're other cases of navigation such as
|
|
31
|
+
// programmatic "shift" navigation or when the user manually clicks "Back" or "Forward" button
|
|
32
|
+
// in a web browser. And even if a "shift" navigation is initiated by the application code,
|
|
33
|
+
// it still doesn't know yet what the new location is gonna be cause it only knows the `delta`.
|
|
34
|
+
// Such "other" cases could only be handled by reacting to an `UPDATE` Redux action
|
|
35
|
+
// which is only emitted after the URL in the browser's address bar has changed.
|
|
36
|
+
//
|
|
37
|
+
// There's no real drawback in reacting to an `UPDATE` Redux action "post factum" because
|
|
38
|
+
// from the application's point of view the address bar doesn't matter and even doesn't exist.
|
|
39
|
+
// All that exists from the application's point of view is the `location` object in the Redux state.
|
|
40
|
+
// Until the `location` object in the Redux state is updated, the old page is still rendered.
|
|
41
|
+
// The appliation is only concerned with the updates of the `location` object in the Redux state
|
|
42
|
+
// and completely ignores any updates to the URL in the web browser's address bar.
|
|
43
|
+
//
|
|
44
|
+
// So here, the "middleware" attempts to prevent or allow navigation that has already happened
|
|
45
|
+
// in the web browser's address bar but hasn't yet happened in Redux state.
|
|
46
|
+
// For example, it could be a user clicking a "Back"/"Forward" button in their web browser.
|
|
47
|
+
// If such navigation should've been blocked, it will simply not update the `locaiton` object in Redux state,
|
|
48
|
+
// and it will also "rewind" the change of the URL in the web browser's address bar so that it's consistent
|
|
49
|
+
// with the `location` in Redux state.
|
|
50
|
+
//
|
|
51
|
+
// eslint-disable-next-line no-underscore-dangle
|
|
52
|
+
case ActionTypesInternal.INTERNAL_LOCATION_UPDATE:
|
|
53
|
+
// Programmatic `PUSH`/`REPLACE` actions are handled in another middleware.
|
|
54
|
+
if (payload.operation === NavigationOperations.PUSH || payload.operation === NavigationOperations.REPLACE) {
|
|
55
|
+
return next(action);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// If no navigation blockers to run, don't do anything.
|
|
59
|
+
if (getNavigationBlockers(session).length === 0) {
|
|
60
|
+
return next(action);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// If it was the initial page load or a redirect,
|
|
64
|
+
// it's not really a navigation that could be rolled back.
|
|
65
|
+
if (payload.delta === 0) {
|
|
66
|
+
return next(action);
|
|
67
|
+
}
|
|
68
|
+
result = runNavigationBlockers(getNavigationBlockers(session),
|
|
69
|
+
// Here `getLocationFromInternalLocation(payload)` is `Location`.
|
|
70
|
+
getLocationFromInternalLocation(payload));
|
|
71
|
+
if (isPromise(result)) {
|
|
72
|
+
const status = createNavigationBlockersEvaluationStatus(session);
|
|
73
|
+
|
|
74
|
+
// While location blockers are running, rewind to the previous location.
|
|
75
|
+
ignoreNavigationLocationSubscriptionEvents(() => {
|
|
76
|
+
session.shift(-payload.delta);
|
|
77
|
+
});
|
|
78
|
+
result.then(promiseResult => {
|
|
79
|
+
if (promiseResult) {
|
|
80
|
+
// Navigation blocked.
|
|
81
|
+
// Already rewound to a previous location.
|
|
82
|
+
} else if (!status.cancelled) {
|
|
83
|
+
// Navigation not blocked.
|
|
84
|
+
// Rewind back to the new location.
|
|
85
|
+
ignoreNavigationLocationSubscriptionEvents(() => {
|
|
86
|
+
session.shift(payload.delta);
|
|
87
|
+
});
|
|
88
|
+
// Update the location.
|
|
89
|
+
next(action);
|
|
90
|
+
}
|
|
91
|
+
});
|
|
92
|
+
} else if (result) {
|
|
93
|
+
// Prevent the navigation: rewind to the previous location.
|
|
94
|
+
ignoreNavigationLocationSubscriptionEvents(() => {
|
|
95
|
+
session.shift(-payload.delta);
|
|
96
|
+
});
|
|
97
|
+
} else {
|
|
98
|
+
// Update the location.
|
|
99
|
+
return next(action);
|
|
100
|
+
}
|
|
101
|
+
// eslint-disable-next-line consistent-return
|
|
102
|
+
return;
|
|
103
|
+
default:
|
|
104
|
+
return next(action);
|
|
105
|
+
}
|
|
106
|
+
};
|
|
107
|
+
};
|
|
108
|
+
}
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import isPromise from '../../isPromise';
|
|
2
|
+
import { getNavigationBlockers, removeAllNavigationBlockers, runNavigationBlockers } from '../../navigationBlockers';
|
|
3
|
+
import ActionTypes from '../ActionTypes';
|
|
4
|
+
export function createNavigationBlockersEvaluationStatus(session) {
|
|
5
|
+
/* eslint-disable no-underscore-dangle */
|
|
6
|
+
if (session._navigationBlockersEvaluationStatus) {
|
|
7
|
+
session._navigationBlockersEvaluationStatus.cancelled = true;
|
|
8
|
+
}
|
|
9
|
+
session._navigationBlockersEvaluationStatus = {
|
|
10
|
+
cancelled: false
|
|
11
|
+
};
|
|
12
|
+
return session._navigationBlockersEvaluationStatus;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
// Creates a "middleware" that applies navigation blockers for programmatic navigation,
|
|
16
|
+
// i.e. the navigation that was triggered by the application code.
|
|
17
|
+
// This only includes `PUSH` and `REPLACE` navigation, and other programmatic types of navigation
|
|
18
|
+
// such as `SHIFT` aren't able to be handled here due to not yet having `location` info,
|
|
19
|
+
// so they're handled in a different middleware.
|
|
20
|
+
export default function createProgrammaticNavigationBlockerMiddleware(session) {
|
|
21
|
+
return function programmaticNavigationBlockerMiddleware() {
|
|
22
|
+
return next => action => {
|
|
23
|
+
const {
|
|
24
|
+
type,
|
|
25
|
+
payload
|
|
26
|
+
} = action;
|
|
27
|
+
|
|
28
|
+
// Declaring `result` variable here fixes ESLint error:
|
|
29
|
+
// "Unexpected lexical declaration in case block".
|
|
30
|
+
let result;
|
|
31
|
+
switch (type) {
|
|
32
|
+
// Prevent or allow navigation that was initiated by the application
|
|
33
|
+
// by dispatching a `.push()` or `.replace()` action.
|
|
34
|
+
//
|
|
35
|
+
// It doesn't handle `.shift()` navigation actions because it doesn't yet know
|
|
36
|
+
// the `location` that it's gonna `shift` to. Instead, it waits for the web browser
|
|
37
|
+
// to "shift" to that `location` and then reads it and rewinds the "shift"
|
|
38
|
+
// if it should've been blocked. That is handled by another "navigation blocker" middleware.
|
|
39
|
+
//
|
|
40
|
+
// This type of "shifting" and then rewinding the "shift" doesn't really matter to the application at all.
|
|
41
|
+
// From the application's point of view, all of that doesn't matter and even doesn't exist.
|
|
42
|
+
// All that exists from the application's point of view is the `location` object in the Redux state.
|
|
43
|
+
// Until the `location` object in the Redux state is updated, the old page is still rendered.
|
|
44
|
+
// The appliation is only concerned with the updates of the `location` object in the Redux state
|
|
45
|
+
// and completely ignores any updates to the URL in the web browser's address bar.
|
|
46
|
+
//
|
|
47
|
+
case ActionTypes.NAVIGATE:
|
|
48
|
+
// `resultValue` variable name works around a stupid javascript error:
|
|
49
|
+
// "Cannot redeclare block-scoped variable 'result'".
|
|
50
|
+
result = runNavigationBlockers(getNavigationBlockers(session),
|
|
51
|
+
// Here `payload.location` is `LocationBase`.
|
|
52
|
+
payload.location);
|
|
53
|
+
if (isPromise(result)) {
|
|
54
|
+
const status = createNavigationBlockersEvaluationStatus(session);
|
|
55
|
+
// eslint-disable-next-line consistent-return
|
|
56
|
+
result.then(resultValue => {
|
|
57
|
+
if (!status.cancelled) {
|
|
58
|
+
if (!resultValue) {
|
|
59
|
+
return next(action);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
});
|
|
63
|
+
} else if (!result) {
|
|
64
|
+
return next(action);
|
|
65
|
+
}
|
|
66
|
+
// eslint-disable-next-line consistent-return
|
|
67
|
+
return;
|
|
68
|
+
|
|
69
|
+
// Programmatic SHIFT actions aren't handled here.
|
|
70
|
+
// Instead, they're handled in non-programmatic navigation blocker middleware.
|
|
71
|
+
// The rationale is that there's no `location` argument on "SHIFT" actions,
|
|
72
|
+
// so the navigation blockers don't know yet what is the new location gonna be.
|
|
73
|
+
// They have to wait for the environment to restore the new location
|
|
74
|
+
// and then tell it to `navigation-stack` by calling a listener.
|
|
75
|
+
case ActionTypes.SHIFT:
|
|
76
|
+
// New `location` isn't known yet. Proceed without blocking anything.
|
|
77
|
+
return next(action);
|
|
78
|
+
|
|
79
|
+
// Remove any navigation blockers on `STOP` event.
|
|
80
|
+
case ActionTypes.STOP:
|
|
81
|
+
removeAllNavigationBlockers(session);
|
|
82
|
+
return next(action);
|
|
83
|
+
default:
|
|
84
|
+
return next(action);
|
|
85
|
+
}
|
|
86
|
+
};
|
|
87
|
+
};
|
|
88
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { removeBasePath } from '../../basePath';
|
|
2
|
+
import ActionTypesInternal from '../ActionTypesInternal';
|
|
3
|
+
|
|
4
|
+
// Creates a "middleware" that, when a website is hosted under a certain path (`basePath`),
|
|
5
|
+
// automatically hides that starting segment from the `pathname` of `location`s.
|
|
6
|
+
export default function createRemoveOutputLocationBasePathMiddleware(basePath) {
|
|
7
|
+
return function removeOutputLocationBasePathMiddleware() {
|
|
8
|
+
return next => action => {
|
|
9
|
+
const {
|
|
10
|
+
type,
|
|
11
|
+
payload
|
|
12
|
+
} = action;
|
|
13
|
+
switch (type) {
|
|
14
|
+
// Transforms `UPDATE` action payload (input `location`).
|
|
15
|
+
case ActionTypesInternal.INTERNAL_LOCATION_UPDATE:
|
|
16
|
+
return next({
|
|
17
|
+
type,
|
|
18
|
+
payload: removeBasePath(payload, basePath)
|
|
19
|
+
});
|
|
20
|
+
default:
|
|
21
|
+
return next(action);
|
|
22
|
+
}
|
|
23
|
+
};
|
|
24
|
+
};
|
|
25
|
+
}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import ActionTypes from '../ActionTypes';
|
|
2
|
+
import ActionTypesInternal from '../ActionTypesInternal';
|
|
3
|
+
|
|
4
|
+
// Creates a "middleware" that performs the actual navigation according to the `session` being used.
|
|
5
|
+
// For example, when `WebBrowserSession` is used, it calls methods of the `window.history` object.
|
|
6
|
+
export default function createUpdateInternalLocationMiddleware(session, {
|
|
7
|
+
shouldIgnoreNavigationLocationSubscriptionEvents
|
|
8
|
+
}) {
|
|
9
|
+
return function updateInternalLocationMiddleware() {
|
|
10
|
+
return next => {
|
|
11
|
+
// Whenever browser location changes,
|
|
12
|
+
// perform the same changes with the internal `location` object.
|
|
13
|
+
const unsubscribe = session.subscribe(location => {
|
|
14
|
+
if (!shouldIgnoreNavigationLocationSubscriptionEvents()) {
|
|
15
|
+
next({
|
|
16
|
+
// eslint-disable-next-line no-underscore-dangle
|
|
17
|
+
type: ActionTypesInternal.INTERNAL_LOCATION_UPDATE,
|
|
18
|
+
payload: location
|
|
19
|
+
});
|
|
20
|
+
}
|
|
21
|
+
});
|
|
22
|
+
return action => {
|
|
23
|
+
const {
|
|
24
|
+
type,
|
|
25
|
+
payload
|
|
26
|
+
} = action;
|
|
27
|
+
switch (type) {
|
|
28
|
+
case ActionTypes.INIT:
|
|
29
|
+
// `init()` will trigger the `subscribe()` listener,
|
|
30
|
+
// which will call `updateLocation()`.
|
|
31
|
+
session.start(payload);
|
|
32
|
+
// eslint-disable-next-line consistent-return
|
|
33
|
+
return;
|
|
34
|
+
case ActionTypes.NAVIGATE:
|
|
35
|
+
// `navigate()` will trigger the `subscribe()` listener,
|
|
36
|
+
// which will call `updateLocation()`.
|
|
37
|
+
session.navigate(payload.operation, payload.location);
|
|
38
|
+
// eslint-disable-next-line consistent-return
|
|
39
|
+
return;
|
|
40
|
+
case ActionTypes.SHIFT:
|
|
41
|
+
// `shift()` will trigger the `subscribe()` listener,
|
|
42
|
+
// which will call `updateLocation()`.
|
|
43
|
+
session.shift(payload);
|
|
44
|
+
// eslint-disable-next-line consistent-return
|
|
45
|
+
return;
|
|
46
|
+
case ActionTypes.STOP:
|
|
47
|
+
// Remove location change subscription.
|
|
48
|
+
unsubscribe();
|
|
49
|
+
// Even if it calls `unsubscribe()` function above, any other subscriptions
|
|
50
|
+
// would still stay. We're not talking about `navigationStack.subscribe()`
|
|
51
|
+
// subscriptions because those don't really matter in terms of cleaning them up:
|
|
52
|
+
// those're just Redux store subscriptions that don't have any side effects.
|
|
53
|
+
// Subscriptions we're talking here are `Session`'s own subscription
|
|
54
|
+
// via `session.subscribe()` and any hypothetical manual `session.subscribe()`
|
|
55
|
+
// calls that could be made by the application code for whatever purpose.
|
|
56
|
+
// Both of those should be cleared.
|
|
57
|
+
// To work around that, `.stop()` function removes all subscriptions.
|
|
58
|
+
session.stop();
|
|
59
|
+
// eslint-disable-next-line consistent-return
|
|
60
|
+
return next(action);
|
|
61
|
+
default:
|
|
62
|
+
// eslint-disable-next-line consistent-return
|
|
63
|
+
return next(action);
|
|
64
|
+
}
|
|
65
|
+
};
|
|
66
|
+
};
|
|
67
|
+
};
|
|
68
|
+
}
|
|
@@ -1,7 +1,8 @@
|
|
|
1
|
+
import Operations from '../../session/navigation/operation/operations';
|
|
1
2
|
import ActionTypes from '../ActionTypes';
|
|
2
3
|
|
|
3
4
|
// This "middleware" transforms a `PUSH` / `REPLACE` action into a `NAVIGATE` action.
|
|
4
|
-
export default function
|
|
5
|
+
export default function navigationOperationMiddleware() {
|
|
5
6
|
return next => action => {
|
|
6
7
|
const {
|
|
7
8
|
type,
|
|
@@ -12,18 +13,20 @@ export default function navigationActionMiddleware() {
|
|
|
12
13
|
case ActionTypes.PUSH:
|
|
13
14
|
return next({
|
|
14
15
|
type: ActionTypes.NAVIGATE,
|
|
15
|
-
payload:
|
|
16
|
-
|
|
17
|
-
|
|
16
|
+
payload: {
|
|
17
|
+
operation: Operations.PUSH,
|
|
18
|
+
location: payload
|
|
19
|
+
}
|
|
18
20
|
});
|
|
19
21
|
|
|
20
22
|
// Converts a `REPLACE` action into a `NAVIGATE` action.
|
|
21
23
|
case ActionTypes.REPLACE:
|
|
22
24
|
return next({
|
|
23
25
|
type: ActionTypes.NAVIGATE,
|
|
24
|
-
payload:
|
|
25
|
-
|
|
26
|
-
|
|
26
|
+
payload: {
|
|
27
|
+
operation: Operations.REPLACE,
|
|
28
|
+
location: payload
|
|
29
|
+
}
|
|
27
30
|
});
|
|
28
31
|
default:
|
|
29
32
|
return next(action);
|
|
@@ -1,19 +1,21 @@
|
|
|
1
|
+
import parseInputLocation from '../../parseInputLocation';
|
|
1
2
|
import ActionTypes from '../ActionTypes';
|
|
2
|
-
import normalizeInputLocation from '../normalizeInputLocation';
|
|
3
3
|
|
|
4
4
|
// This "middleware" transforms input location argument into a proper `NormalizedInputLocation`.
|
|
5
|
-
export default function
|
|
5
|
+
export default function parseInputLocationMiddleware() {
|
|
6
6
|
return next => action => {
|
|
7
7
|
const {
|
|
8
8
|
type,
|
|
9
9
|
payload
|
|
10
10
|
} = action;
|
|
11
11
|
switch (type) {
|
|
12
|
+
case ActionTypes.INIT:
|
|
12
13
|
case ActionTypes.PUSH:
|
|
13
14
|
case ActionTypes.REPLACE:
|
|
14
15
|
return next({
|
|
15
16
|
type,
|
|
16
|
-
payload
|
|
17
|
+
// `payload` is optional in `INIT` action.
|
|
18
|
+
payload: payload && parseInputLocation(payload)
|
|
17
19
|
});
|
|
18
20
|
default:
|
|
19
21
|
return next(action);
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import getLocationFromInternalLocation from '../../getLocationFromInternalLocation';
|
|
2
|
+
import ActionTypes from '../ActionTypes';
|
|
3
|
+
import ActionTypesInternal from '../ActionTypesInternal';
|
|
4
|
+
export default function updateLocationMiddleware() {
|
|
5
|
+
return next => action => {
|
|
6
|
+
const {
|
|
7
|
+
type,
|
|
8
|
+
payload
|
|
9
|
+
} = action;
|
|
10
|
+
switch (type) {
|
|
11
|
+
// Convert `LocationInternal` object to a publicly-visible `Location` object.
|
|
12
|
+
// It hides non-essential properties of location such as `operation`, `index`, `delta`.
|
|
13
|
+
// eslint-disable-next-line no-underscore-dangle
|
|
14
|
+
case ActionTypesInternal.INTERNAL_LOCATION_UPDATE:
|
|
15
|
+
// Dispatch a "public" `UPDATE` Redux action.
|
|
16
|
+
// This is what users of this package should use
|
|
17
|
+
// rather than `INTERNAL_LOCATION_UPDATE` which is for internal purposes.
|
|
18
|
+
next({
|
|
19
|
+
type: ActionTypes.UPDATE,
|
|
20
|
+
payload: getLocationFromInternalLocation(payload)
|
|
21
|
+
});
|
|
22
|
+
return;
|
|
23
|
+
default:
|
|
24
|
+
// eslint-disable-next-line consistent-return
|
|
25
|
+
return next(action);
|
|
26
|
+
}
|
|
27
|
+
};
|
|
28
|
+
}
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
/* eslint-disable no-underscore-dangle */
|
|
2
|
+
|
|
3
|
+
import scheduleNextTick from './scheduleNextTick';
|
|
4
|
+
|
|
5
|
+
// The original author of `scroll-behavior` package wrote:
|
|
6
|
+
//
|
|
7
|
+
// "Updating the window scroll position is really flaky.
|
|
8
|
+
// Just trying to scroll it isn't enough.
|
|
9
|
+
// Instead, try to scroll a few times until it works."
|
|
10
|
+
//
|
|
11
|
+
// What it does here is it scrolls two times:
|
|
12
|
+
// * First time at the moment of calling the `.set()` method.
|
|
13
|
+
// * Second time after a momentary delay.
|
|
14
|
+
//
|
|
15
|
+
export default class PageScrollPositionSetter {
|
|
16
|
+
// Sets page scroll position either at an "anchor" or at given coordinates.
|
|
17
|
+
_setPageScrollPositionTo(scrollPositionOrAnchor, environmentScrollPosition) {
|
|
18
|
+
if (typeof scrollPositionOrAnchor === 'string') {
|
|
19
|
+
// Scrolls page to an "ahcnor".
|
|
20
|
+
environmentScrollPosition.setPageScrollPositionAtAnchor(scrollPositionOrAnchor);
|
|
21
|
+
} else {
|
|
22
|
+
// Scrolls page to given coordinates.
|
|
23
|
+
environmentScrollPosition.setPageScrollPosition(scrollPositionOrAnchor);
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
_setPageScrollPosition(environmentScrollPosition) {
|
|
27
|
+
const isDelayedCall = Boolean(this._cancelDelayedSetPageScrollPosition);
|
|
28
|
+
|
|
29
|
+
// If this function was triggered in a delayed fashion,
|
|
30
|
+
// clear the reference to the "cancel" function because it's no longer of use.
|
|
31
|
+
if (isDelayedCall) {
|
|
32
|
+
this._cancelDelayedSetPageScrollPosition = null;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// It's not really possible for `this._pageScrollPositionOrAnchorToSet` to be `null` or `undefined` at this point.
|
|
36
|
+
// Still, this `if` condition acts as a "foolproof" redundant check.
|
|
37
|
+
/* istanbul ignore if: paranoid guard */
|
|
38
|
+
if (!this._pageScrollPositionOrAnchorToSet) {
|
|
39
|
+
return Promise.resolve();
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// The original author of `scroll-behavior` package wrote:
|
|
43
|
+
//
|
|
44
|
+
// "Updating the window scroll position is really flaky.
|
|
45
|
+
// Just trying to scroll it isn't enough.
|
|
46
|
+
// Instead, try to scroll a few times until it works."
|
|
47
|
+
//
|
|
48
|
+
this._setPageScrollPositionTo(this._pageScrollPositionOrAnchorToSet, environmentScrollPosition);
|
|
49
|
+
|
|
50
|
+
// If it was a delayed call, stop.
|
|
51
|
+
if (isDelayedCall) {
|
|
52
|
+
this._resetScrollPositionOrAnchorToSet();
|
|
53
|
+
return Promise.resolve();
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// Repeat the attempt to set scroll position after a momentary delay.
|
|
57
|
+
return new Promise(resolve => {
|
|
58
|
+
this._cancelDelayedSetPageScrollPosition = scheduleNextTick(() => resolve(this._setPageScrollPosition(environmentScrollPosition)));
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// Sets scroll position at an anchor or at given coordinates.
|
|
63
|
+
set(scrollableContainer, pageScrollPositionOrAnchor, environmentScrollPosition) {
|
|
64
|
+
// Prevents empty string anchor.
|
|
65
|
+
if (!pageScrollPositionOrAnchor) {
|
|
66
|
+
throw new Error('Argument is required');
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// Validate that no `scrollableContainer` is passed.
|
|
70
|
+
if (scrollableContainer) {
|
|
71
|
+
throw new Error('`scrollableContainer` argument should not be provided because `PageScrollPositionSetter` was only designed to set scroll position of a page');
|
|
72
|
+
}
|
|
73
|
+
this.cancel();
|
|
74
|
+
this._pageScrollPositionOrAnchorToSet = pageScrollPositionOrAnchor;
|
|
75
|
+
return this._setPageScrollPosition(environmentScrollPosition);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// This function should be "idempotent", i.e. be able to be called multiple times.
|
|
79
|
+
cancel() {
|
|
80
|
+
if (this._pageScrollPositionOrAnchorToSet) {
|
|
81
|
+
this._resetScrollPositionOrAnchorToSet();
|
|
82
|
+
if (this._cancelDelayedSetPageScrollPosition) {
|
|
83
|
+
this._cancelDelayedSetPageScrollPosition();
|
|
84
|
+
this._cancelDelayedSetPageScrollPosition = undefined;
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
_resetScrollPositionOrAnchorToSet() {
|
|
89
|
+
this._pageScrollPositionOrAnchorToSet = undefined;
|
|
90
|
+
}
|
|
91
|
+
}
|