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
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
/* eslint-disable no-underscore-dangle */
|
|
2
|
+
|
|
3
|
+
import { PAGE_SCROLLABLE_CONTAINER_KEY } from './constants';
|
|
4
|
+
import scheduleNextTick from './scheduleNextTick';
|
|
5
|
+
|
|
6
|
+
export default class ScrollPositionAutoSaver {
|
|
7
|
+
constructor({
|
|
8
|
+
scrollPosition,
|
|
9
|
+
scrollPositionSaver,
|
|
10
|
+
getScrollableContainers,
|
|
11
|
+
shouldSaveScrollPosition,
|
|
12
|
+
}) {
|
|
13
|
+
this._scrollPosition = scrollPosition;
|
|
14
|
+
this._scrollPositionSaver = scrollPositionSaver;
|
|
15
|
+
this._shouldSaveScrollPosition = shouldSaveScrollPosition;
|
|
16
|
+
|
|
17
|
+
this._getScrollableContainers = getScrollableContainers;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
// Starts auto-saving of scroll positions.
|
|
21
|
+
start() {
|
|
22
|
+
// Get scrollable containers.
|
|
23
|
+
const scrollableContainers = this._getScrollableContainers();
|
|
24
|
+
|
|
25
|
+
// Set up scroll listeners on scrollable containers.
|
|
26
|
+
for (const scrollableContainerKey of Object.keys(scrollableContainers)) {
|
|
27
|
+
if (scrollableContainerKey === PAGE_SCROLLABLE_CONTAINER_KEY) {
|
|
28
|
+
this.addPageScrollListener();
|
|
29
|
+
} else {
|
|
30
|
+
this.addScrollableContainerScrollListener(scrollableContainerKey);
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// Stops auto-saving of scroll positions.
|
|
36
|
+
stop() {
|
|
37
|
+
// Get scrollable containers.
|
|
38
|
+
const scrollableContainers = this._getScrollableContainers();
|
|
39
|
+
|
|
40
|
+
// Remove scroll listeners on scrollable containers.
|
|
41
|
+
for (const scrollableContainerKey of Object.keys(scrollableContainers)) {
|
|
42
|
+
if (scrollableContainerKey === PAGE_SCROLLABLE_CONTAINER_KEY) {
|
|
43
|
+
// If there's any scheduled saving of page scroll position, cancel it.
|
|
44
|
+
this.cancelSavePageScrollPosition();
|
|
45
|
+
// Remove scroll listener on the page.
|
|
46
|
+
this.removePageScrollListener();
|
|
47
|
+
} else {
|
|
48
|
+
this.cancelSaveScrollableContainerScrollPosition(
|
|
49
|
+
scrollableContainerKey,
|
|
50
|
+
);
|
|
51
|
+
this.removeScrollableContainerScrollListener(scrollableContainerKey);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
cancelScheduledAutoSave() {
|
|
57
|
+
for (const scrollableContainerKey of Object.keys(
|
|
58
|
+
this._getScrollableContainers(),
|
|
59
|
+
)) {
|
|
60
|
+
if (scrollableContainerKey === PAGE_SCROLLABLE_CONTAINER_KEY) {
|
|
61
|
+
this.cancelSavePageScrollPosition();
|
|
62
|
+
} else {
|
|
63
|
+
this.cancelSaveScrollableContainerScrollPosition(
|
|
64
|
+
scrollableContainerKey,
|
|
65
|
+
);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
cancelSavePageScrollPosition() {
|
|
71
|
+
if (this._cancelSavePageScrollPosition) {
|
|
72
|
+
this._cancelSavePageScrollPosition();
|
|
73
|
+
this._cancelSavePageScrollPosition = null;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
cancelSaveScrollableContainerScrollPosition(scrollableContainerKey) {
|
|
78
|
+
const scrollableContainerEntry =
|
|
79
|
+
this._getScrollableContainers()[scrollableContainerKey];
|
|
80
|
+
if (scrollableContainerEntry.cancelSaveScrollPosition) {
|
|
81
|
+
scrollableContainerEntry.cancelSaveScrollPosition();
|
|
82
|
+
scrollableContainerEntry.cancelSaveScrollPosition = null;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
removePageScrollListener() {
|
|
87
|
+
// Remove scroll listener on the page.
|
|
88
|
+
if (this._removePageScrollListener) {
|
|
89
|
+
this._removePageScrollListener();
|
|
90
|
+
this._removePageScrollListener = null;
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
removeScrollableContainerScrollListener(scrollableContainerKey) {
|
|
95
|
+
const scrollableContainerEntry =
|
|
96
|
+
this._getScrollableContainers()[scrollableContainerKey];
|
|
97
|
+
if (scrollableContainerEntry.removeScrollListener) {
|
|
98
|
+
scrollableContainerEntry.removeScrollListener();
|
|
99
|
+
scrollableContainerEntry.removeScrollListener = null;
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
addScrollableContainerScrollListener(scrollableContainerKey) {
|
|
104
|
+
const scrollableContainerEntry =
|
|
105
|
+
this._getScrollableContainers()[scrollableContainerKey];
|
|
106
|
+
|
|
107
|
+
scrollableContainerEntry.removeScrollListener =
|
|
108
|
+
this._scrollPosition.addScrollableContainerScrollListener(
|
|
109
|
+
scrollableContainerEntry.scrollableContainer,
|
|
110
|
+
() => {
|
|
111
|
+
// This flag is not used in real life and is only used in tests (for some reason).
|
|
112
|
+
if (!this._shouldSaveScrollPosition()) {
|
|
113
|
+
return;
|
|
114
|
+
}
|
|
115
|
+
// Use `scheduleNextTick()` function to "throttle" incoming scroll events.
|
|
116
|
+
// There would be no use in reacting to every incoming scroll event
|
|
117
|
+
// because there might be too many in a given short period of time
|
|
118
|
+
// which could affect the performance of the application.
|
|
119
|
+
if (!scrollableContainerEntry.cancelSaveScrollPosition) {
|
|
120
|
+
scrollableContainerEntry.cancelSaveScrollPosition =
|
|
121
|
+
scheduleNextTick(() => {
|
|
122
|
+
this._scrollPositionSaver.saveScrollableContainerScrollPosition(
|
|
123
|
+
scrollableContainerKey,
|
|
124
|
+
scrollableContainerEntry.scrollableContainer,
|
|
125
|
+
);
|
|
126
|
+
});
|
|
127
|
+
}
|
|
128
|
+
},
|
|
129
|
+
);
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
addPageScrollListener() {
|
|
133
|
+
// Set up scroll listener on the page.
|
|
134
|
+
this._removePageScrollListener =
|
|
135
|
+
this._scrollPosition.addPageScrollListener(() => {
|
|
136
|
+
// This flag is not used in real life and is only used in tests (for some reason).
|
|
137
|
+
if (!this._shouldSaveScrollPosition()) {
|
|
138
|
+
return;
|
|
139
|
+
}
|
|
140
|
+
// Use `scheduleNextTick()` function to "throttle" incoming scroll events.
|
|
141
|
+
// There would be no use in reacting to every incoming scroll event
|
|
142
|
+
// because there might be too many in a given short period of time
|
|
143
|
+
// which could affect the performance of the application.
|
|
144
|
+
if (!this._cancelSavePageScrollPosition) {
|
|
145
|
+
this._cancelSavePageScrollPosition = scheduleNextTick(() => {
|
|
146
|
+
this._scrollPositionSaver.savePageScrollPosition();
|
|
147
|
+
});
|
|
148
|
+
}
|
|
149
|
+
});
|
|
150
|
+
}
|
|
151
|
+
}
|
|
@@ -0,0 +1,506 @@
|
|
|
1
|
+
/* eslint-disable no-underscore-dangle */
|
|
2
|
+
|
|
3
|
+
import PageScrollPositionSetter from './PageScrollPositionSetter';
|
|
4
|
+
import ScrollPositionSaver from './ScrollPositionSaver';
|
|
5
|
+
import ScrollPositionSetter from './ScrollPositionSetter';
|
|
6
|
+
import { PAGE_SCROLLABLE_CONTAINER_KEY } from './constants';
|
|
7
|
+
import LocationDataStorage from '../data-storage/LocationDataStorage';
|
|
8
|
+
|
|
9
|
+
function areEqualScrollPositions(scrollPosition1, scrollPosition2) {
|
|
10
|
+
let i = 0;
|
|
11
|
+
while (i < scrollPosition1.length) {
|
|
12
|
+
if (scrollPosition1[i] !== scrollPosition2[i]) {
|
|
13
|
+
return false;
|
|
14
|
+
}
|
|
15
|
+
i++;
|
|
16
|
+
}
|
|
17
|
+
return true;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export default class ScrollPositionRestoration {
|
|
21
|
+
constructor(session, _options) {
|
|
22
|
+
this._scrollPosition = session.environment.scrollPosition;
|
|
23
|
+
|
|
24
|
+
this._sessionLifecycle = session.lifecycle;
|
|
25
|
+
|
|
26
|
+
this._locationDataStorage = new LocationDataStorage(session, {
|
|
27
|
+
namespace: 'navigation-stack/scroll-position',
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
this._scrollPositionSaver = new ScrollPositionSaver({
|
|
31
|
+
scrollPosition: this._scrollPosition,
|
|
32
|
+
saveScrollPositionForLocation: this._saveScrollPositionForLocation,
|
|
33
|
+
getScrollableContainers: () => this._scrollableContainers,
|
|
34
|
+
getLocation: () => this._location,
|
|
35
|
+
shouldSaveScrollPosition: () => !this._doNotSaveScrollPosition,
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
// Originally, `scrollableContainerKey` was auto-generated,
|
|
39
|
+
// but then it didn't work with the concept of dynamically adding or removing
|
|
40
|
+
// scrollable containers after `ScrollPositionRestoration` has already started.
|
|
41
|
+
//
|
|
42
|
+
// this._scrollableContainerKeyCounter = 0;
|
|
43
|
+
|
|
44
|
+
this._scrollableContainers = {};
|
|
45
|
+
|
|
46
|
+
// Add page scrollable container.
|
|
47
|
+
this._scrollableContainers[PAGE_SCROLLABLE_CONTAINER_KEY] = {
|
|
48
|
+
scrollableContainer: undefined,
|
|
49
|
+
|
|
50
|
+
// Using this option, a developer could theoretically provide their own implementation
|
|
51
|
+
// of setting a scroll position. For example, it could use "smooth" (animated) scrolling, etc.
|
|
52
|
+
// This could be part of the public API if anyone provided a sensible real-world use case for it.
|
|
53
|
+
scrollPositionSetter:
|
|
54
|
+
(_options && _options._pageScrollPositionSetter) ||
|
|
55
|
+
// The default page scroll position setter.
|
|
56
|
+
new PageScrollPositionSetter(),
|
|
57
|
+
|
|
58
|
+
// This function is only used in tests.
|
|
59
|
+
// There seems to be no use of it in real life, hence it's not public API.
|
|
60
|
+
// It's only used in tests.
|
|
61
|
+
_getScrollPositionForLocation:
|
|
62
|
+
_options && _options._getPageScrollPositionForLocation,
|
|
63
|
+
|
|
64
|
+
// This function is only used in tests.
|
|
65
|
+
// There seems to be no use of it in real life, hence it's not public API.
|
|
66
|
+
// It's only used in tests.
|
|
67
|
+
_shouldUpdateScrollPositionForLocation:
|
|
68
|
+
_options && _options._shouldUpdatePageScrollPositionForLocation,
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
addScrollableContainer(
|
|
73
|
+
scrollableContainerKey,
|
|
74
|
+
scrollableContainer,
|
|
75
|
+
_options,
|
|
76
|
+
) {
|
|
77
|
+
// Originally, `scrollableContainerKey` was auto-generated,
|
|
78
|
+
// but then it didn't work with the concept of dynamically adding or removing
|
|
79
|
+
// scrollable containers after `ScrollPositionRestoration` has already started.
|
|
80
|
+
//
|
|
81
|
+
// this._scrollableContainerKeyCounter++;
|
|
82
|
+
// const scrollableContainerKey = String(this._scrollableContainerKeyCounter);
|
|
83
|
+
|
|
84
|
+
if (scrollableContainerKey === PAGE_SCROLLABLE_CONTAINER_KEY) {
|
|
85
|
+
throw new Error(
|
|
86
|
+
`Scrollable container key "${scrollableContainerKey}" is not allowed`,
|
|
87
|
+
);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// Add scrollable container entry.
|
|
91
|
+
this._scrollableContainers[scrollableContainerKey] = {
|
|
92
|
+
// Scrollable container element.
|
|
93
|
+
scrollableContainer,
|
|
94
|
+
|
|
95
|
+
// Using this option, a developer could theoretically provide their own implementation
|
|
96
|
+
// of setting a scroll position. For example, it could use "smooth" (animated) scrolling, etc.
|
|
97
|
+
// This could be part of the public API if anyone provided a sensible real-world use case for it.
|
|
98
|
+
scrollPositionSetter:
|
|
99
|
+
(_options && _options._scrollPositionSetter) ||
|
|
100
|
+
// The default basic "immediate" scroll position setter.
|
|
101
|
+
new ScrollPositionSetter(),
|
|
102
|
+
|
|
103
|
+
// This function is only used in tests.
|
|
104
|
+
// There seems to be no use of it in real life, hence it's not public API.
|
|
105
|
+
// It's only used in tests.
|
|
106
|
+
_shouldUpdateScrollPositionForLocation:
|
|
107
|
+
_options && _options._shouldUpdateScrollPositionForLocation,
|
|
108
|
+
|
|
109
|
+
// This function is only used in tests.
|
|
110
|
+
// There seems to be no use of it in real life, hence it's not public API.
|
|
111
|
+
// It's only used in tests.
|
|
112
|
+
_getScrollPositionForLocation:
|
|
113
|
+
_options && _options._getScrollPositionForLocation,
|
|
114
|
+
};
|
|
115
|
+
|
|
116
|
+
// Scrollable containers could be added at any time, including page mount.
|
|
117
|
+
// For example, a user navigates "Back" to a previous page where there's
|
|
118
|
+
// a "unique" scrollable container that's only present on that page.
|
|
119
|
+
// In that case, the previously-saved scroll position inside the scrollable container
|
|
120
|
+
// should be restored, if it pre-exists. Otherwise, if it doesn't pre-exist,
|
|
121
|
+
// the initial scroll position should be saved for the scrollable container.
|
|
122
|
+
if (this._location) {
|
|
123
|
+
const previouslySavedScrollPosition =
|
|
124
|
+
this._getSavedScrollPositionForLocation(
|
|
125
|
+
this._location,
|
|
126
|
+
scrollableContainerKey,
|
|
127
|
+
);
|
|
128
|
+
if (previouslySavedScrollPosition) {
|
|
129
|
+
this._scrollPosition.setScrollableContainerScrollPosition(
|
|
130
|
+
scrollableContainer,
|
|
131
|
+
previouslySavedScrollPosition,
|
|
132
|
+
);
|
|
133
|
+
} else {
|
|
134
|
+
this._scrollPositionSaver.saveScrollableContainerScrollPosition(
|
|
135
|
+
scrollableContainerKey,
|
|
136
|
+
scrollableContainer,
|
|
137
|
+
);
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
if (this._started) {
|
|
142
|
+
this._scrollPositionSaver._scrollPositionAutoSaver.addScrollableContainerScrollListener(
|
|
143
|
+
scrollableContainerKey,
|
|
144
|
+
);
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
// Removes the scrollable container.
|
|
148
|
+
return () => {
|
|
149
|
+
this._scrollPositionSaver._scrollPositionAutoSaver.cancelSaveScrollableContainerScrollPosition(
|
|
150
|
+
scrollableContainerKey,
|
|
151
|
+
);
|
|
152
|
+
this._scrollPositionSaver._scrollPositionAutoSaver.removeScrollableContainerScrollListener(
|
|
153
|
+
scrollableContainerKey,
|
|
154
|
+
);
|
|
155
|
+
delete this._scrollableContainers[scrollableContainerKey];
|
|
156
|
+
};
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
start() {
|
|
160
|
+
// "Foolproof" check.
|
|
161
|
+
if (this._started) {
|
|
162
|
+
throw new Error('Already started');
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
this._started = true;
|
|
166
|
+
|
|
167
|
+
this._disableAutomaticScrollRestoration();
|
|
168
|
+
|
|
169
|
+
this._scrollPositionSaver.start();
|
|
170
|
+
|
|
171
|
+
this._removePageStatusListener =
|
|
172
|
+
this._sessionLifecycle.addExecutionStatusListener(
|
|
173
|
+
this._sessionExecutionStatusListener,
|
|
174
|
+
);
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
// This method is "idempotent", i.e. it can be called multiple times.
|
|
178
|
+
stop() {
|
|
179
|
+
// "Foolproof" check.
|
|
180
|
+
if (!this._started) {
|
|
181
|
+
return;
|
|
182
|
+
// throw new Error('Not started');
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
this._started = false;
|
|
186
|
+
|
|
187
|
+
this._enableAutomaticScrollRestoration();
|
|
188
|
+
|
|
189
|
+
// If there's any scroll position still scheduled to be set, cancel it.
|
|
190
|
+
this._cancelAnyPendingSettingOfScrollPosition();
|
|
191
|
+
|
|
192
|
+
this._scrollPositionSaver.stop();
|
|
193
|
+
|
|
194
|
+
this._removePageStatusListener();
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
_cancelAnyPendingSettingOfScrollPosition() {
|
|
198
|
+
for (const scrollableContainerEntry of Object.values(
|
|
199
|
+
this._scrollableContainers,
|
|
200
|
+
)) {
|
|
201
|
+
scrollableContainerEntry.scrollPositionSetter.cancel();
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
// Once configured, scroll restoration mode persists across page reloads.
|
|
206
|
+
// I.e. even if a user refreshes the page in a web browser, the custom
|
|
207
|
+
// `window.history.scrollRestoration` value will still remain.
|
|
208
|
+
//
|
|
209
|
+
// And since it's set to a custom value of "manual", the web browser
|
|
210
|
+
// won't attempt to restore the scroll position on page load
|
|
211
|
+
// which it would otherwise normally do.
|
|
212
|
+
//
|
|
213
|
+
// So what happens if the website is fully server-side rendered?
|
|
214
|
+
// It will wait for the javascript code to be downloaded an executed first
|
|
215
|
+
// and only then that javascript code will programmatically restore the
|
|
216
|
+
// previously-saved scroll position.
|
|
217
|
+
//
|
|
218
|
+
// That would work but it also wouldn't be the most efficient way to do that.
|
|
219
|
+
// Instead, `window.history.scrollRestoration` value could be reset to default beforehand
|
|
220
|
+
// so that when the page finishes refreshing, the web browser could automatically
|
|
221
|
+
// restore the scroll position without waiting for the javascript code to download and run.
|
|
222
|
+
//
|
|
223
|
+
// To reset `window.history.scrollRestoration` value to default beforehand,
|
|
224
|
+
// the code should be notified when the browser tab is about to be terminated or suspended.
|
|
225
|
+
// Terminating could happen for various reasons such as not enough memory, code crash, etc.
|
|
226
|
+
// Suspending is treated equally to terminating because once suspended, it could potentially be
|
|
227
|
+
// terminated afterwards without the code being able to do its stuff while it's suspended.
|
|
228
|
+
//
|
|
229
|
+
// One could consider this feature a minor user experience optimization that relies on the web browser
|
|
230
|
+
// to correctly restore the page scroll every time on page refresh, which it normally does.
|
|
231
|
+
//
|
|
232
|
+
_sessionExecutionStatusListener = ({ running }) => {
|
|
233
|
+
if (running) {
|
|
234
|
+
this._disableAutomaticScrollRestoration();
|
|
235
|
+
} else {
|
|
236
|
+
this._enableAutomaticScrollRestoration();
|
|
237
|
+
|
|
238
|
+
// There might be previous scroll position already saved in the data storage.
|
|
239
|
+
// Overwrite that previously-saved scroll position with the most up-to-date one
|
|
240
|
+
// just so that there's no stale scroll position left over in the data storage.
|
|
241
|
+
// Alternatively, it could just clear any saved scroll position for this page,
|
|
242
|
+
// since the web browser's automatic scroll restoration is now enabled.
|
|
243
|
+
this._scrollPositionSaver.saveScrollPosition();
|
|
244
|
+
}
|
|
245
|
+
};
|
|
246
|
+
|
|
247
|
+
// willRenderLocation = (location) => {
|
|
248
|
+
// // "Foolproof" check.
|
|
249
|
+
// if (!this._started) {
|
|
250
|
+
// throw new Error('`ScrollPositionRestoration` not started');
|
|
251
|
+
// }
|
|
252
|
+
//
|
|
253
|
+
// // For the initial location, it doesn't do anything.
|
|
254
|
+
// if (location.operation === Operations.INIT) {
|
|
255
|
+
// return;
|
|
256
|
+
// }
|
|
257
|
+
//
|
|
258
|
+
// // Since the current page will no longer be rendered,
|
|
259
|
+
// // cancel any scheduled setting of scroll position on it.
|
|
260
|
+
// this._cancelAnyPendingSettingOfScrollPosition();
|
|
261
|
+
//
|
|
262
|
+
// // The previous page may have scheduled an auto-save of scroll position.
|
|
263
|
+
// // Since the previous page is no longer rendered, its scroll position can no longer be obtained,
|
|
264
|
+
// // so any scheduled scroll position auto-save produres are irrelevant now.
|
|
265
|
+
// this._scrollPositionSaver.cancelPreviouslyScheduledAutoSave();
|
|
266
|
+
//
|
|
267
|
+
// // Save the current scroll position on the current page while it's still rendered.
|
|
268
|
+
// // This saved scroll position could later be restored in case of returing to this page.
|
|
269
|
+
// this._scrollPositionSaver.saveScrollPosition();
|
|
270
|
+
// };
|
|
271
|
+
|
|
272
|
+
locationRendered(location) {
|
|
273
|
+
// Validate that `location` has a `key`.
|
|
274
|
+
if (!location.key) {
|
|
275
|
+
throw new Error('`location` must have a `key`');
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
this._prevLocation = this._location;
|
|
279
|
+
this._location = location;
|
|
280
|
+
|
|
281
|
+
this._scrollPosition.init();
|
|
282
|
+
|
|
283
|
+
if (!this._started) {
|
|
284
|
+
// `this.start()` requires `this._location` to be set.
|
|
285
|
+
this.start();
|
|
286
|
+
|
|
287
|
+
// The initial page might've been server-side rendered which means that
|
|
288
|
+
// by the time this javascript code is downloaded and executed by the web browser,
|
|
289
|
+
// the user might've already scrolled the page to some position,
|
|
290
|
+
// and all those pre-javascript scroll events won't be registered by `ScrollPositionSaver`.
|
|
291
|
+
// If the user doesn't scroll after javascript is loaded and just navigates to a new page,
|
|
292
|
+
// the initial page won't have any saved scroll position to restore on "Back" navigation.
|
|
293
|
+
// Hence, it should explicitly save the current scroll position at the start of operation.
|
|
294
|
+
if (!this._isDefaultScrollPosition()) {
|
|
295
|
+
// `this._scrollPositionSaver.saveScrollPosition()` requires `this._location` to be set.
|
|
296
|
+
this._scrollPositionSaver.saveScrollPosition();
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
// The previous page may have scheduled an auto-save of scroll position.
|
|
301
|
+
// Since the previous page is no longer rendered, its scroll position can no longer be obtained,
|
|
302
|
+
// so any scheduled scroll position auto-save produres are irrelevant now.
|
|
303
|
+
this._scrollPositionSaver.cancelPreviouslyScheduledAutoSave();
|
|
304
|
+
|
|
305
|
+
// If it was in the middle of setting scroll position for a previous location, cancel it.
|
|
306
|
+
this._cancelAnyPendingSettingOfScrollPosition();
|
|
307
|
+
|
|
308
|
+
// Set the scroll position for the new page:
|
|
309
|
+
// either restore a previously-saved one or set it to a default scroll position.
|
|
310
|
+
this._setScrollPosition();
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
// Tells if the current scroll position is the default one.
|
|
314
|
+
_isDefaultScrollPosition() {
|
|
315
|
+
for (const scrollableContainerKey of Object.keys(
|
|
316
|
+
this._scrollableContainers,
|
|
317
|
+
)) {
|
|
318
|
+
if (scrollableContainerKey === PAGE_SCROLLABLE_CONTAINER_KEY) {
|
|
319
|
+
if (
|
|
320
|
+
!areEqualScrollPositions(
|
|
321
|
+
this._scrollPosition.getPageScrollPosition(),
|
|
322
|
+
this._getDefaultScrollPosition(),
|
|
323
|
+
)
|
|
324
|
+
) {
|
|
325
|
+
return false;
|
|
326
|
+
}
|
|
327
|
+
} else {
|
|
328
|
+
const scrollableContainerEntry =
|
|
329
|
+
this._scrollableContainers[scrollableContainerKey];
|
|
330
|
+
if (
|
|
331
|
+
!areEqualScrollPositions(
|
|
332
|
+
this._scrollPosition.getScrollableContainerScrollPosition(
|
|
333
|
+
scrollableContainerEntry.scrollableContainer,
|
|
334
|
+
),
|
|
335
|
+
this._getDefaultScrollPosition(),
|
|
336
|
+
)
|
|
337
|
+
) {
|
|
338
|
+
return false;
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
return true;
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
_setScrollPosition() {
|
|
347
|
+
for (const scrollableContainerKey of Object.keys(
|
|
348
|
+
this._scrollableContainers,
|
|
349
|
+
)) {
|
|
350
|
+
const scrollableContainerEntry =
|
|
351
|
+
this._scrollableContainers[scrollableContainerKey];
|
|
352
|
+
|
|
353
|
+
// This function is only used in tests.
|
|
354
|
+
// There seems to be no use of it in real life, hence it's not public API.
|
|
355
|
+
// It's only used in tests.
|
|
356
|
+
if (scrollableContainerEntry._shouldUpdateScrollPositionForLocation) {
|
|
357
|
+
if (
|
|
358
|
+
!scrollableContainerEntry._shouldUpdateScrollPositionForLocation(
|
|
359
|
+
this._location,
|
|
360
|
+
this._prevLocation,
|
|
361
|
+
)
|
|
362
|
+
) {
|
|
363
|
+
continue;
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
// Scroll position (or anchor) to set.
|
|
368
|
+
let scrollPositionOrAnchorToSet;
|
|
369
|
+
|
|
370
|
+
// This function is only used in tests.
|
|
371
|
+
// There seems to be no use of it in real life, hence it's not public API.
|
|
372
|
+
// It's only used in tests.
|
|
373
|
+
if (scrollableContainerEntry._getScrollPositionForLocation) {
|
|
374
|
+
scrollPositionOrAnchorToSet =
|
|
375
|
+
scrollableContainerEntry._getScrollPositionForLocation(
|
|
376
|
+
this._location,
|
|
377
|
+
this._prevLocation,
|
|
378
|
+
);
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
// Get scroll position (or anchor) to set.
|
|
382
|
+
if (!scrollPositionOrAnchorToSet) {
|
|
383
|
+
scrollPositionOrAnchorToSet =
|
|
384
|
+
scrollableContainerKey === PAGE_SCROLLABLE_CONTAINER_KEY
|
|
385
|
+
? this._getPageScrollPositionOrAnchorToSet(this._location)
|
|
386
|
+
: this._getScrollableContainerScrollPositionToSet(
|
|
387
|
+
this._location,
|
|
388
|
+
scrollableContainerKey,
|
|
389
|
+
);
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
// Set scroll position of scrollable container.
|
|
393
|
+
scrollableContainerEntry.scrollPositionSetter.set(
|
|
394
|
+
scrollableContainerEntry.scrollableContainer,
|
|
395
|
+
scrollPositionOrAnchorToSet,
|
|
396
|
+
this._scrollPosition,
|
|
397
|
+
);
|
|
398
|
+
}
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
// Overrides the default `window.history.scrollRestoration` value.
|
|
402
|
+
// This prevents the web browser from interfering by disabling its
|
|
403
|
+
// automatic scroll position restoration on "Back"/"Forward" navigation.
|
|
404
|
+
// Instead, the application will have to do it manually.
|
|
405
|
+
// The reason is that when the web browser performs "Back" or "Forward" navigation,
|
|
406
|
+
// it updates the URL in the address bar immediately, and it also attempts to
|
|
407
|
+
// automatically restore scroll position immediately, but the thing is that
|
|
408
|
+
// the application might have delayed rendering of the page due to various reasons
|
|
409
|
+
// such as performance considerations or the architecture of the rendering framework.
|
|
410
|
+
// For example, React framework by design renders pages in "asynchronous" fashion.
|
|
411
|
+
// Hence, by the time the web browser attempts to restore the scroll position,
|
|
412
|
+
// the page might not yet be rendered which would result in incorrect scroll position restoration.
|
|
413
|
+
// That's why the application has to take over this functionality from the web browser.
|
|
414
|
+
_disableAutomaticScrollRestoration = () => {
|
|
415
|
+
try {
|
|
416
|
+
this._scrollPosition.disableAutomaticScrollRestoration();
|
|
417
|
+
} catch (error) {
|
|
418
|
+
// eslint-disable-next-line no-console
|
|
419
|
+
console.error(
|
|
420
|
+
'[navigation-stack] could not disable default scroll restoration mode',
|
|
421
|
+
);
|
|
422
|
+
}
|
|
423
|
+
};
|
|
424
|
+
|
|
425
|
+
_enableAutomaticScrollRestoration = () => {
|
|
426
|
+
try {
|
|
427
|
+
this._scrollPosition.enableAutomaticScrollRestoration();
|
|
428
|
+
} catch (error) {
|
|
429
|
+
// eslint-disable-next-line no-console
|
|
430
|
+
console.error(
|
|
431
|
+
'[navigation-stack] could not enable default scroll restoration mode',
|
|
432
|
+
);
|
|
433
|
+
}
|
|
434
|
+
};
|
|
435
|
+
|
|
436
|
+
_getSavedScrollPositionForLocation(
|
|
437
|
+
location,
|
|
438
|
+
scrollableContainerKey = PAGE_SCROLLABLE_CONTAINER_KEY,
|
|
439
|
+
) {
|
|
440
|
+
return this._locationDataStorage.get(location, scrollableContainerKey);
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
_saveScrollPositionForLocation = (
|
|
444
|
+
location,
|
|
445
|
+
scrollableContainerKey,
|
|
446
|
+
scrollPosition,
|
|
447
|
+
) => {
|
|
448
|
+
this._locationDataStorage.set(
|
|
449
|
+
location,
|
|
450
|
+
scrollableContainerKey || PAGE_SCROLLABLE_CONTAINER_KEY,
|
|
451
|
+
scrollPosition,
|
|
452
|
+
);
|
|
453
|
+
};
|
|
454
|
+
|
|
455
|
+
// Returns scroll position coordinates or an anchor name.
|
|
456
|
+
_getPageScrollPositionOrAnchorToSet(location) {
|
|
457
|
+
// If it's a return to a previously-visited location,
|
|
458
|
+
// read the saved scroll position from session data store.
|
|
459
|
+
return (
|
|
460
|
+
this._getSavedScrollPositionForLocation(location) ||
|
|
461
|
+
this._getAnchor(location) ||
|
|
462
|
+
this._getDefaultScrollPosition()
|
|
463
|
+
);
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
// Returns scroll position coordinates.
|
|
467
|
+
_getScrollableContainerScrollPositionToSet(
|
|
468
|
+
location,
|
|
469
|
+
scrollableContainerKey,
|
|
470
|
+
) {
|
|
471
|
+
// If it's a return to a previously-visited location,
|
|
472
|
+
// read the saved scroll position from session data store.
|
|
473
|
+
return (
|
|
474
|
+
this._getSavedScrollPositionForLocation(
|
|
475
|
+
location,
|
|
476
|
+
scrollableContainerKey,
|
|
477
|
+
) || this._getDefaultScrollPosition()
|
|
478
|
+
);
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
_getAnchor(location) {
|
|
482
|
+
const { hash } = location;
|
|
483
|
+
if (hash && hash !== '#') {
|
|
484
|
+
return hash.slice('#'.length);
|
|
485
|
+
}
|
|
486
|
+
return undefined;
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
_getDefaultScrollPosition() {
|
|
490
|
+
return [0, 0];
|
|
491
|
+
}
|
|
492
|
+
|
|
493
|
+
// `_enableSavingScrollPosition()` and `_disableSavingScrollPosition()`
|
|
494
|
+
// aren't used in real life and are not part of the public API.
|
|
495
|
+
// They're only used in tests.
|
|
496
|
+
_enableSavingScrollPosition() {
|
|
497
|
+
this._doNotSaveScrollPosition = undefined;
|
|
498
|
+
}
|
|
499
|
+
|
|
500
|
+
// `_enableSavingScrollPosition()` and `_disableSavingScrollPosition()`
|
|
501
|
+
// aren't used in real life and are not part of the public API.
|
|
502
|
+
// They're only used in tests.
|
|
503
|
+
_disableSavingScrollPosition() {
|
|
504
|
+
this._doNotSaveScrollPosition = true;
|
|
505
|
+
}
|
|
506
|
+
}
|