navigation-stack 0.3.1 → 0.5.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.
Files changed (257) hide show
  1. package/CHANGELOG.md +12 -0
  2. package/README.md +611 -163
  3. package/data-storage/package.json +6 -0
  4. package/karma.conf.cjs +21 -4
  5. package/lib/cjs/NavigationStack.js +88 -0
  6. package/lib/cjs/data-storage/DataStorage.js +71 -0
  7. package/lib/cjs/data-storage/LocationDataStorage.js +29 -0
  8. package/lib/cjs/data-storage/index.js +9 -0
  9. package/lib/cjs/debug.js +12 -0
  10. package/lib/cjs/environment/InMemoryEnvironment.js +15 -0
  11. package/lib/cjs/environment/WebBrowserEnvironment.js +15 -0
  12. package/lib/cjs/environment/data-storage/InMemoryDataStorage.js +27 -0
  13. package/lib/cjs/environment/data-storage/WebBrowserDataStorage.js +21 -0
  14. package/lib/cjs/environment/scroll-position/InMemoryScrollPosition.js +44 -0
  15. package/lib/cjs/environment/scroll-position/WebBrowserScrollPosition.js +60 -0
  16. package/lib/cjs/getLocationFromInternalLocation.js +14 -0
  17. package/lib/cjs/index.js +20 -16
  18. package/lib/cjs/navigationBlockers.js +28 -23
  19. package/lib/cjs/{normalizeInputLocation.js → parseInputLocation.js} +25 -9
  20. package/lib/cjs/{ActionTypes.js → redux/ActionTypes.js} +1 -1
  21. package/lib/cjs/redux/ActionTypesInternal.js +8 -0
  22. package/lib/cjs/{Actions.js → redux/Actions.js} +5 -4
  23. package/lib/cjs/redux/createMiddlewares.js +60 -0
  24. package/lib/cjs/redux/index.js +13 -0
  25. package/lib/cjs/redux/internalLocationReducer.js +14 -0
  26. package/lib/cjs/redux/middleware/createAddInputLocationBasePathMiddleware.js +32 -0
  27. package/lib/cjs/redux/middleware/createNonProgrammaticNavigationBlockerMiddleware.js +113 -0
  28. package/lib/cjs/redux/middleware/createProgrammaticNavigationBlockerMiddleware.js +94 -0
  29. package/lib/cjs/redux/middleware/createRemoveOutputLocationBasePathMiddleware.js +30 -0
  30. package/lib/cjs/redux/middleware/createUpdateInternalLocationMiddleware.js +73 -0
  31. package/lib/cjs/{middleware/navigationActionMiddleware.js → redux/middleware/navigationOperationMiddleware.js} +11 -8
  32. package/lib/cjs/{middleware/normalizeInputLocationMiddleware.js → redux/middleware/parseInputLocationMiddleware.js} +6 -4
  33. package/lib/cjs/redux/middleware/updateLocationMiddleware.js +34 -0
  34. package/lib/cjs/scroll-position/PageScrollPositionSetter.js +97 -0
  35. package/lib/cjs/scroll-position/ScrollPositionAutoSaver.js +141 -0
  36. package/lib/cjs/scroll-position/ScrollPositionRestoration.js +407 -0
  37. package/lib/cjs/scroll-position/ScrollPositionSaver.js +87 -0
  38. package/lib/cjs/scroll-position/ScrollPositionSetter.js +16 -0
  39. package/lib/cjs/scroll-position/constants.js +5 -0
  40. package/lib/cjs/scroll-position/index.js +7 -0
  41. package/lib/cjs/scroll-position/scheduleNextTick.js +11 -0
  42. package/lib/cjs/session/InMemorySession.js +22 -0
  43. package/lib/cjs/session/ServerSideRenderSession.js +17 -0
  44. package/lib/cjs/session/Session.js +202 -0
  45. package/lib/cjs/session/WebBrowserSession.js +20 -0
  46. package/lib/cjs/session/key/createSessionKey.js +23 -0
  47. package/lib/cjs/session/lifecycle/InMemorySessionLifecycle.js +19 -0
  48. package/lib/cjs/session/lifecycle/WebBrowserSessionLifecycle.js +128 -0
  49. package/lib/cjs/session/lifecycle/page-lifecycle/PageLifecycle.js +269 -0
  50. package/lib/cjs/session/lifecycle/page-lifecycle/PageLifecycleInstance.js +8 -0
  51. package/lib/cjs/session/lifecycle/page-lifecycle/supportsConstructableEventTarget.js +33 -0
  52. package/lib/cjs/session/navigation/InMemoryNavigation.js +104 -0
  53. package/lib/cjs/session/navigation/ServerSideNavigation.js +61 -0
  54. package/lib/cjs/session/navigation/WebBrowserNavigation.js +221 -0
  55. package/lib/cjs/session/navigation/error/NavigationOutOfBoundsError.js +12 -0
  56. package/lib/cjs/session/navigation/error/ServerSideNavigationError.js +21 -0
  57. package/lib/cjs/session/navigation/operation/operations.js +11 -0
  58. package/lib/cjs/session/subscription/Subscription.js +81 -0
  59. package/lib/data-storage/index.d.ts +35 -0
  60. package/lib/esm/NavigationStack.js +81 -0
  61. package/lib/esm/data-storage/DataStorage.js +65 -0
  62. package/lib/esm/data-storage/LocationDataStorage.js +22 -0
  63. package/lib/esm/data-storage/index.js +2 -0
  64. package/lib/esm/debug.js +7 -0
  65. package/lib/esm/environment/InMemoryEnvironment.js +8 -0
  66. package/lib/esm/environment/WebBrowserEnvironment.js +8 -0
  67. package/lib/esm/environment/data-storage/InMemoryDataStorage.js +21 -0
  68. package/lib/esm/environment/data-storage/WebBrowserDataStorage.js +15 -0
  69. package/lib/esm/environment/scroll-position/InMemoryScrollPosition.js +38 -0
  70. package/lib/esm/environment/scroll-position/WebBrowserScrollPosition.js +54 -0
  71. package/lib/esm/getLocationFromInternalLocation.js +9 -0
  72. package/lib/esm/index.js +10 -8
  73. package/lib/esm/navigationBlockers.js +28 -23
  74. package/lib/esm/{normalizeInputLocation.js → parseInputLocation.js} +24 -8
  75. package/lib/esm/{ActionTypes.js → redux/ActionTypes.js} +1 -1
  76. package/lib/esm/redux/ActionTypesInternal.js +3 -0
  77. package/lib/esm/{Actions.js → redux/Actions.js} +5 -4
  78. package/lib/esm/redux/createMiddlewares.js +54 -0
  79. package/lib/esm/redux/index.js +4 -0
  80. package/lib/esm/redux/internalLocationReducer.js +8 -0
  81. package/lib/esm/redux/middleware/createAddInputLocationBasePathMiddleware.js +27 -0
  82. package/lib/esm/redux/middleware/createNonProgrammaticNavigationBlockerMiddleware.js +108 -0
  83. package/lib/esm/redux/middleware/createProgrammaticNavigationBlockerMiddleware.js +88 -0
  84. package/lib/esm/redux/middleware/createRemoveOutputLocationBasePathMiddleware.js +25 -0
  85. package/lib/esm/redux/middleware/createUpdateInternalLocationMiddleware.js +68 -0
  86. package/lib/esm/{middleware/navigationActionMiddleware.js → redux/middleware/navigationOperationMiddleware.js} +10 -7
  87. package/lib/esm/{middleware/normalizeInputLocationMiddleware.js → redux/middleware/parseInputLocationMiddleware.js} +5 -3
  88. package/lib/esm/redux/middleware/updateLocationMiddleware.js +28 -0
  89. package/lib/esm/scroll-position/PageScrollPositionSetter.js +91 -0
  90. package/lib/esm/scroll-position/ScrollPositionAutoSaver.js +134 -0
  91. package/lib/esm/scroll-position/ScrollPositionRestoration.js +400 -0
  92. package/lib/esm/scroll-position/ScrollPositionSaver.js +80 -0
  93. package/lib/esm/scroll-position/ScrollPositionSetter.js +10 -0
  94. package/lib/esm/scroll-position/constants.js +1 -0
  95. package/lib/esm/scroll-position/index.js +1 -0
  96. package/lib/esm/scroll-position/scheduleNextTick.js +6 -0
  97. package/lib/esm/session/InMemorySession.js +15 -0
  98. package/lib/esm/session/ServerSideRenderSession.js +11 -0
  99. package/lib/esm/session/Session.js +195 -0
  100. package/lib/esm/session/WebBrowserSession.js +13 -0
  101. package/lib/esm/session/key/createSessionKey.js +18 -0
  102. package/lib/esm/session/lifecycle/InMemorySessionLifecycle.js +13 -0
  103. package/lib/esm/session/lifecycle/WebBrowserSessionLifecycle.js +120 -0
  104. package/lib/esm/session/lifecycle/page-lifecycle/PageLifecycle.js +263 -0
  105. package/lib/esm/session/lifecycle/page-lifecycle/PageLifecycleInstance.js +2 -0
  106. package/lib/esm/session/lifecycle/page-lifecycle/supportsConstructableEventTarget.js +30 -0
  107. package/lib/esm/session/navigation/InMemoryNavigation.js +97 -0
  108. package/lib/esm/session/navigation/ServerSideNavigation.js +54 -0
  109. package/lib/esm/session/navigation/WebBrowserNavigation.js +213 -0
  110. package/lib/esm/session/navigation/error/NavigationOutOfBoundsError.js +6 -0
  111. package/lib/esm/session/navigation/error/ServerSideNavigationError.js +14 -0
  112. package/lib/esm/session/navigation/operation/operations.js +6 -0
  113. package/lib/esm/session/subscription/Subscription.js +75 -0
  114. package/lib/index.d.ts +179 -157
  115. package/lib/redux/index.d.ts +90 -0
  116. package/lib/scroll-position/index.d.ts +107 -0
  117. package/package.json +9 -5
  118. package/redux/package.json +6 -0
  119. package/scroll-position/package.json +6 -0
  120. package/src/NavigationStack.js +100 -0
  121. package/src/data-storage/DataStorage.js +69 -0
  122. package/src/data-storage/LocationDataStorage.js +23 -0
  123. package/src/data-storage/index.js +2 -0
  124. package/src/debug.js +8 -0
  125. package/src/environment/InMemoryEnvironment.js +9 -0
  126. package/src/environment/WebBrowserEnvironment.js +9 -0
  127. package/src/environment/data-storage/InMemoryDataStorage.js +23 -0
  128. package/src/environment/data-storage/WebBrowserDataStorage.js +17 -0
  129. package/src/environment/scroll-position/InMemoryScrollPosition.js +45 -0
  130. package/src/environment/scroll-position/WebBrowserScrollPosition.js +72 -0
  131. package/src/getLocationFromInternalLocation.js +7 -0
  132. package/src/index.js +10 -8
  133. package/src/navigationBlockers.js +31 -27
  134. package/src/{normalizeInputLocation.js → parseInputLocation.js} +23 -8
  135. package/src/{ActionTypes.js → redux/ActionTypes.js} +1 -1
  136. package/src/redux/ActionTypesInternal.js +3 -0
  137. package/src/{Actions.js → redux/Actions.js} +4 -3
  138. package/src/redux/createMiddlewares.js +65 -0
  139. package/src/redux/index.js +4 -0
  140. package/src/redux/internalLocationReducer.js +9 -0
  141. package/src/redux/middleware/createAddInputLocationBasePathMiddleware.js +27 -0
  142. package/src/redux/middleware/createNonProgrammaticNavigationBlockerMiddleware.js +119 -0
  143. package/src/redux/middleware/createProgrammaticNavigationBlockerMiddleware.js +94 -0
  144. package/src/redux/middleware/createRemoveOutputLocationBasePathMiddleware.js +26 -0
  145. package/src/redux/middleware/createUpdateInternalLocationMiddleware.js +72 -0
  146. package/src/{middleware/navigationActionMiddleware.js → redux/middleware/navigationOperationMiddleware.js} +10 -3
  147. package/src/{middleware/normalizeInputLocationMiddleware.js → redux/middleware/parseInputLocationMiddleware.js} +5 -3
  148. package/src/redux/middleware/updateLocationMiddleware.js +28 -0
  149. package/src/scroll-position/PageScrollPositionSetter.js +110 -0
  150. package/src/scroll-position/ScrollPositionAutoSaver.js +168 -0
  151. package/src/scroll-position/ScrollPositionRestoration.js +551 -0
  152. package/src/scroll-position/ScrollPositionSaver.js +120 -0
  153. package/src/scroll-position/ScrollPositionSetter.js +16 -0
  154. package/src/scroll-position/constants.js +1 -0
  155. package/src/scroll-position/index.js +1 -0
  156. package/src/scroll-position/scheduleNextTick.js +6 -0
  157. package/src/session/InMemorySession.js +13 -0
  158. package/src/session/ServerSideRenderSession.js +9 -0
  159. package/src/session/Session.js +238 -0
  160. package/src/session/WebBrowserSession.js +13 -0
  161. package/src/session/key/createSessionKey.js +18 -0
  162. package/src/session/lifecycle/InMemorySessionLifecycle.js +13 -0
  163. package/src/session/lifecycle/WebBrowserSessionLifecycle.js +126 -0
  164. package/src/session/lifecycle/page-lifecycle/PageLifecycle.js +291 -0
  165. package/src/session/lifecycle/page-lifecycle/PageLifecycleInstance.js +3 -0
  166. package/src/session/lifecycle/page-lifecycle/supportsConstructableEventTarget.js +32 -0
  167. package/src/session/navigation/InMemoryNavigation.js +78 -0
  168. package/src/session/navigation/ServerSideNavigation.js +43 -0
  169. package/src/session/navigation/WebBrowserNavigation.js +224 -0
  170. package/src/session/navigation/error/NavigationOutOfBoundsError.js +7 -0
  171. package/src/session/navigation/error/ServerSideNavigationError.js +18 -0
  172. package/src/session/navigation/operation/operations.js +6 -0
  173. package/src/session/subscription/Subscription.js +76 -0
  174. package/test/NavigationStack.test.js +296 -0
  175. package/test/{LocationDataStorage.test.js → data-storage/LocationDataStorage.test.js} +3 -3
  176. package/test/data-storage/index.test.js +8 -0
  177. package/test/index.js +12 -0
  178. package/test/index.test.js +8 -7
  179. package/test/{helpers.js → middlewareTestUtil.js} +9 -12
  180. package/test/{normalizeInputLocation.test.js → parseInputLocationMiddleware.test.js} +9 -9
  181. package/test/{Action.test.js → redux/Action.test.js} +7 -6
  182. package/test/{ActionTypes.test.js → redux/ActionTypes.test.js} +2 -2
  183. package/test/redux/createMiddlewares.test.js +96 -0
  184. package/test/redux/index.test.js +10 -0
  185. package/test/{locationReducer.test.js → redux/locationReducer.test.js} +4 -7
  186. package/test/redux/middleware/createAddInputLocationBasePathMiddleware.test.js +40 -0
  187. package/test/redux/middleware/createNonProgrammaticNavigationBlockerMiddleware.test.js +264 -0
  188. package/test/redux/middleware/createProgrammaticNavigationBlockerMiddleware.test.js +312 -0
  189. package/test/redux/middleware/createRemoveOutputLocationBasePathMiddleware.test.js +51 -0
  190. package/test/{middleware/navigationActionMiddleware.test.js → redux/middleware/navigationOperationMiddleware.test.js} +16 -12
  191. package/test/{middleware/normalizeInputLocationMiddleware.test.js → redux/middleware/parseInputLocationMiddleware.test.js} +4 -4
  192. package/test/scroll-position/ScrollPositionRestoration.test.js +435 -0
  193. package/test/scroll-position/addScrollableContainer.js +39 -0
  194. package/test/scroll-position/addScrollableContainerWithAnchors.js +56 -0
  195. package/test/scroll-position/createApp.js +132 -0
  196. package/test/scroll-position/delay.js +9 -0
  197. package/test/scroll-position/mockPageLifecycle.js +17 -0
  198. package/test/scroll-position/runApp.js +24 -0
  199. package/test/scroll-position/withScrollableContainerAtIndexPageWithDisabledAutomaticScrollPositionRestoration.js +72 -0
  200. package/test/session/InMemorySession.test.js +348 -0
  201. package/test/session/ServerSession.test.js +17 -9
  202. package/test/session/WebBrowserSession.test.js +265 -0
  203. package/test/testUtil.js +3 -0
  204. package/types/data-storage/index.d.ts +35 -0
  205. package/types/index.d.ts +179 -157
  206. package/types/redux/index.d.ts +90 -0
  207. package/types/scroll-position/index.d.ts +107 -0
  208. package/types/tsconfig.json +1 -1
  209. package/lib/cjs/LocationDataStorage.js +0 -61
  210. package/lib/cjs/addBeforeLocationChangeListener.js +0 -7
  211. package/lib/cjs/beforeLocationChangeListeners.js +0 -51
  212. package/lib/cjs/createMiddlewares.js +0 -47
  213. package/lib/cjs/middleware/createBasePathMiddleware.js +0 -24
  214. package/lib/cjs/middleware/createBeforeLocationChangeListenerMiddleware.js +0 -39
  215. package/lib/cjs/middleware/createLocationMiddleware.js +0 -56
  216. package/lib/cjs/middleware/createNavigationBlockerMiddleware.js +0 -161
  217. package/lib/cjs/middleware/createTransformLocationMiddleware.js +0 -38
  218. package/lib/cjs/onlyAllowedOnClientSide.js +0 -10
  219. package/lib/cjs/session/BrowserSession.js +0 -235
  220. package/lib/cjs/session/MemorySession.js +0 -223
  221. package/lib/cjs/session/ServerSession.js +0 -65
  222. package/lib/esm/LocationDataStorage.js +0 -54
  223. package/lib/esm/addBeforeLocationChangeListener.js +0 -2
  224. package/lib/esm/beforeLocationChangeListeners.js +0 -44
  225. package/lib/esm/createMiddlewares.js +0 -41
  226. package/lib/esm/middleware/createBasePathMiddleware.js +0 -19
  227. package/lib/esm/middleware/createBeforeLocationChangeListenerMiddleware.js +0 -34
  228. package/lib/esm/middleware/createLocationMiddleware.js +0 -50
  229. package/lib/esm/middleware/createNavigationBlockerMiddleware.js +0 -156
  230. package/lib/esm/middleware/createTransformLocationMiddleware.js +0 -33
  231. package/lib/esm/onlyAllowedOnClientSide.js +0 -5
  232. package/lib/esm/session/BrowserSession.js +0 -229
  233. package/lib/esm/session/MemorySession.js +0 -217
  234. package/lib/esm/session/ServerSession.js +0 -58
  235. package/src/LocationDataStorage.js +0 -60
  236. package/src/addBeforeLocationChangeListener.js +0 -2
  237. package/src/beforeLocationChangeListeners.js +0 -54
  238. package/src/createMiddlewares.js +0 -45
  239. package/src/middleware/createBasePathMiddleware.js +0 -20
  240. package/src/middleware/createBeforeLocationChangeListenerMiddleware.js +0 -40
  241. package/src/middleware/createLocationMiddleware.js +0 -55
  242. package/src/middleware/createNavigationBlockerMiddleware.js +0 -168
  243. package/src/middleware/createTransformLocationMiddleware.js +0 -29
  244. package/src/onlyAllowedOnClientSide.js +0 -5
  245. package/src/session/BrowserSession.js +0 -235
  246. package/src/session/MemorySession.js +0 -219
  247. package/src/session/ServerSession.js +0 -67
  248. package/test/createMiddlewares.test.js +0 -62
  249. package/test/middleware/createBasePathMiddleware.test.js +0 -67
  250. package/test/middleware/createBeforeLocationChangeListenerMiddleware.test.js +0 -141
  251. package/test/middleware/createNavigationBlockerMiddleware.test.js +0 -471
  252. package/test/middleware/createTransformLocationMiddleware.test.js +0 -44
  253. package/test/session/BrowserSession.test.js +0 -182
  254. package/test/session/MemorySession.test.js +0 -244
  255. /package/lib/cjs/{locationReducer.js → redux/locationReducer.js} +0 -0
  256. /package/lib/esm/{locationReducer.js → redux/locationReducer.js} +0 -0
  257. /package/src/{locationReducer.js → redux/locationReducer.js} +0 -0
@@ -0,0 +1,97 @@
1
+ "use strict";
2
+
3
+ exports.__esModule = true;
4
+ exports.default = void 0;
5
+ var _scheduleNextTick = _interopRequireDefault(require("./scheduleNextTick"));
6
+ function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
7
+ /* eslint-disable no-underscore-dangle */
8
+
9
+ // The original author of `scroll-behavior` package wrote:
10
+ //
11
+ // "Updating the window scroll position is really flaky.
12
+ // Just trying to scroll it isn't enough.
13
+ // Instead, try to scroll a few times until it works."
14
+ //
15
+ // What it does here is it scrolls two times:
16
+ // * First time at the moment of calling the `.set()` method.
17
+ // * Second time after a momentary delay.
18
+ //
19
+ class PageScrollPositionSetter {
20
+ // Sets page scroll position either at an "anchor" or at given coordinates.
21
+ _setPageScrollPositionTo(scrollPositionOrAnchor, environmentScrollPosition) {
22
+ if (typeof scrollPositionOrAnchor === 'string') {
23
+ // Scrolls page to an "ahcnor".
24
+ environmentScrollPosition.setPageScrollPositionAtAnchor(scrollPositionOrAnchor);
25
+ } else {
26
+ // Scrolls page to given coordinates.
27
+ environmentScrollPosition.setPageScrollPosition(scrollPositionOrAnchor);
28
+ }
29
+ }
30
+ _setPageScrollPosition(environmentScrollPosition) {
31
+ const isDelayedCall = Boolean(this._cancelDelayedSetPageScrollPosition);
32
+
33
+ // If this function was triggered in a delayed fashion,
34
+ // clear the reference to the "cancel" function because it's no longer of use.
35
+ if (isDelayedCall) {
36
+ this._cancelDelayedSetPageScrollPosition = null;
37
+ }
38
+
39
+ // It's not really possible for `this._pageScrollPositionOrAnchorToSet` to be `null` or `undefined` at this point.
40
+ // Still, this `if` condition acts as a "foolproof" redundant check.
41
+ /* istanbul ignore if: paranoid guard */
42
+ if (!this._pageScrollPositionOrAnchorToSet) {
43
+ return Promise.resolve();
44
+ }
45
+
46
+ // The original author of `scroll-behavior` package wrote:
47
+ //
48
+ // "Updating the window scroll position is really flaky.
49
+ // Just trying to scroll it isn't enough.
50
+ // Instead, try to scroll a few times until it works."
51
+ //
52
+ this._setPageScrollPositionTo(this._pageScrollPositionOrAnchorToSet, environmentScrollPosition);
53
+
54
+ // If it was a delayed call, stop.
55
+ if (isDelayedCall) {
56
+ this._resetScrollPositionOrAnchorToSet();
57
+ return Promise.resolve();
58
+ }
59
+
60
+ // Repeat the attempt to set scroll position after a momentary delay.
61
+ return new Promise(resolve => {
62
+ this._cancelDelayedSetPageScrollPosition = (0, _scheduleNextTick.default)(() => resolve(this._setPageScrollPosition(environmentScrollPosition)));
63
+ });
64
+ }
65
+
66
+ // Sets scroll position at an anchor or at given coordinates.
67
+ set(scrollableContainer, pageScrollPositionOrAnchor, environmentScrollPosition) {
68
+ // Prevents empty string anchor.
69
+ if (!pageScrollPositionOrAnchor) {
70
+ throw new Error('Argument is required');
71
+ }
72
+
73
+ // Validate that no `scrollableContainer` is passed.
74
+ if (scrollableContainer) {
75
+ throw new Error('`scrollableContainer` argument should not be provided because `PageScrollPositionSetter` was only designed to set scroll position of a page');
76
+ }
77
+ this.cancel();
78
+ this._pageScrollPositionOrAnchorToSet = pageScrollPositionOrAnchor;
79
+ return this._setPageScrollPosition(environmentScrollPosition);
80
+ }
81
+
82
+ // This function should be "idempotent", i.e. be able to be called multiple times.
83
+ cancel() {
84
+ if (this._pageScrollPositionOrAnchorToSet) {
85
+ this._resetScrollPositionOrAnchorToSet();
86
+ if (this._cancelDelayedSetPageScrollPosition) {
87
+ this._cancelDelayedSetPageScrollPosition();
88
+ this._cancelDelayedSetPageScrollPosition = undefined;
89
+ }
90
+ }
91
+ }
92
+ _resetScrollPositionOrAnchorToSet() {
93
+ this._pageScrollPositionOrAnchorToSet = undefined;
94
+ }
95
+ }
96
+ exports.default = PageScrollPositionSetter;
97
+ module.exports = exports.default;
@@ -0,0 +1,141 @@
1
+ "use strict";
2
+
3
+ exports.__esModule = true;
4
+ exports.default = void 0;
5
+ var _constants = require("./constants");
6
+ var _scheduleNextTick = _interopRequireDefault(require("./scheduleNextTick"));
7
+ var _debug = _interopRequireDefault(require("../debug"));
8
+ function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
9
+ /* eslint-disable no-underscore-dangle */
10
+
11
+ class ScrollPositionAutoSaver {
12
+ constructor({
13
+ scrollPosition,
14
+ scrollPositionSaver,
15
+ getScrollableContainers,
16
+ shouldSaveScrollPosition
17
+ }) {
18
+ this._scrollPosition = scrollPosition;
19
+ this._scrollPositionSaver = scrollPositionSaver;
20
+ this._shouldSaveScrollPosition = shouldSaveScrollPosition;
21
+ this._getScrollableContainers = getScrollableContainers;
22
+ }
23
+
24
+ // Starts auto-saving of scroll positions.
25
+ start() {
26
+ // Get scrollable containers.
27
+ const scrollableContainers = this._getScrollableContainers();
28
+
29
+ // Set up scroll listeners on scrollable containers.
30
+ for (const scrollableContainerKey of Object.keys(scrollableContainers)) {
31
+ if (scrollableContainerKey === _constants.PAGE_SCROLLABLE_CONTAINER_KEY) {
32
+ this.addPageScrollListener();
33
+ } else {
34
+ this.addScrollableContainerScrollListener(scrollableContainerKey);
35
+ }
36
+ }
37
+ }
38
+
39
+ // Stops auto-saving of scroll positions.
40
+ stop() {
41
+ // Get scrollable containers.
42
+ const scrollableContainers = this._getScrollableContainers();
43
+
44
+ // Remove scroll listeners on scrollable containers.
45
+ for (const scrollableContainerKey of Object.keys(scrollableContainers)) {
46
+ if (scrollableContainerKey === _constants.PAGE_SCROLLABLE_CONTAINER_KEY) {
47
+ // If there's any scheduled saving of page scroll position, cancel it.
48
+ this.cancelSavePageScrollPosition();
49
+ // Remove scroll listener on the page.
50
+ this.removePageScrollListener();
51
+ } else {
52
+ this.cancelSaveScrollableContainerScrollPosition(scrollableContainerKey);
53
+ this.removeScrollableContainerScrollListener(scrollableContainerKey);
54
+ }
55
+ }
56
+ }
57
+ cancelScheduledAutoSave() {
58
+ for (const scrollableContainerKey of Object.keys(this._getScrollableContainers())) {
59
+ if (scrollableContainerKey === _constants.PAGE_SCROLLABLE_CONTAINER_KEY) {
60
+ this.cancelSavePageScrollPosition();
61
+ } else {
62
+ this.cancelSaveScrollableContainerScrollPosition(scrollableContainerKey);
63
+ }
64
+ }
65
+ }
66
+ cancelSavePageScrollPosition(hasRun) {
67
+ if (this._cancelSavePageScrollPosition) {
68
+ if (!hasRun) {
69
+ (0, _debug.default)('cancel delayed save scroll position', _constants.PAGE_SCROLLABLE_CONTAINER_KEY);
70
+ }
71
+ this._cancelSavePageScrollPosition();
72
+ this._cancelSavePageScrollPosition = null;
73
+ }
74
+ }
75
+ cancelSaveScrollableContainerScrollPosition(scrollableContainerKey, hasRun) {
76
+ const scrollableContainerEntry = this._getScrollableContainers()[scrollableContainerKey];
77
+ if (scrollableContainerEntry.cancelSaveScrollPosition) {
78
+ if (!hasRun) {
79
+ (0, _debug.default)('cancel delayed save scroll position', scrollableContainerKey);
80
+ }
81
+ scrollableContainerEntry.cancelSaveScrollPosition();
82
+ scrollableContainerEntry.cancelSaveScrollPosition = null;
83
+ }
84
+ }
85
+ removePageScrollListener() {
86
+ // Remove scroll listener on the page.
87
+ if (this._removePageScrollListener) {
88
+ this._removePageScrollListener();
89
+ this._removePageScrollListener = null;
90
+ }
91
+ }
92
+ removeScrollableContainerScrollListener(scrollableContainerKey) {
93
+ const scrollableContainerEntry = this._getScrollableContainers()[scrollableContainerKey];
94
+ if (scrollableContainerEntry.removeScrollListener) {
95
+ scrollableContainerEntry.removeScrollListener();
96
+ scrollableContainerEntry.removeScrollListener = null;
97
+ }
98
+ }
99
+ addScrollableContainerScrollListener(scrollableContainerKey) {
100
+ const scrollableContainerEntry = this._getScrollableContainers()[scrollableContainerKey];
101
+ scrollableContainerEntry.removeScrollListener = this._scrollPosition.addScrollableContainerScrollListener(scrollableContainerEntry.scrollableContainer, () => {
102
+ // This flag is not used in real life and is only used in tests (for some reason).
103
+ if (!this._shouldSaveScrollPosition()) {
104
+ return;
105
+ }
106
+ // Use `scheduleNextTick()` function to "throttle" incoming scroll events.
107
+ // There would be no use in reacting to every incoming scroll event
108
+ // because there might be too many in a given short period of time
109
+ // which could affect the performance of the application.
110
+ if (!scrollableContainerEntry.cancelSaveScrollPosition) {
111
+ (0, _debug.default)('scroll detected', scrollableContainerKey);
112
+ scrollableContainerEntry.cancelSaveScrollPosition = (0, _scheduleNextTick.default)(() => {
113
+ (0, _debug.default)('auto-save scroll position after scroll', scrollableContainerKey);
114
+ this._scrollPositionSaver.saveScrollableContainerScrollPosition(scrollableContainerKey, scrollableContainerEntry.scrollableContainer);
115
+ });
116
+ }
117
+ });
118
+ }
119
+ addPageScrollListener() {
120
+ // Set up scroll listener on the page.
121
+ this._removePageScrollListener = this._scrollPosition.addPageScrollListener(() => {
122
+ (0, _debug.default)('scroll detected', _constants.PAGE_SCROLLABLE_CONTAINER_KEY);
123
+
124
+ // This flag is not used in real life and is only used in tests (for some reason).
125
+ if (!this._shouldSaveScrollPosition()) {
126
+ return;
127
+ }
128
+ // Use `scheduleNextTick()` function to "throttle" incoming scroll events.
129
+ // There would be no use in reacting to every incoming scroll event
130
+ // because there might be too many in a given short period of time
131
+ // which could affect the performance of the application.
132
+ if (!this._cancelSavePageScrollPosition) {
133
+ this._cancelSavePageScrollPosition = (0, _scheduleNextTick.default)(() => {
134
+ this._scrollPositionSaver.savePageScrollPosition();
135
+ });
136
+ }
137
+ });
138
+ }
139
+ }
140
+ exports.default = ScrollPositionAutoSaver;
141
+ module.exports = exports.default;
@@ -0,0 +1,407 @@
1
+ "use strict";
2
+
3
+ exports.__esModule = true;
4
+ exports.default = void 0;
5
+ var _PageScrollPositionSetter = _interopRequireDefault(require("./PageScrollPositionSetter"));
6
+ var _ScrollPositionSaver = _interopRequireDefault(require("./ScrollPositionSaver"));
7
+ var _ScrollPositionSetter = _interopRequireDefault(require("./ScrollPositionSetter"));
8
+ var _constants = require("./constants");
9
+ var _LocationDataStorage = _interopRequireDefault(require("../data-storage/LocationDataStorage"));
10
+ var _debug = _interopRequireDefault(require("../debug"));
11
+ function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
12
+ /* eslint-disable no-underscore-dangle */
13
+
14
+ function areEqualScrollPositions(scrollPosition1, scrollPosition2) {
15
+ let i = 0;
16
+ while (i < scrollPosition1.length) {
17
+ if (scrollPosition1[i] !== scrollPosition2[i]) {
18
+ return false;
19
+ }
20
+ i++;
21
+ }
22
+ return true;
23
+ }
24
+ class ScrollPositionRestoration {
25
+ constructor(session, _options) {
26
+ // Once configured, scroll restoration mode persists across page reloads.
27
+ // I.e. even if a user refreshes the page in a web browser, the custom
28
+ // `window.history.scrollRestoration` value will still remain.
29
+ //
30
+ // And since it's set to a custom value of "manual", the web browser
31
+ // won't attempt to restore the scroll position on page load
32
+ // which it would otherwise normally do.
33
+ //
34
+ // So what happens if the website is fully server-side rendered?
35
+ // It will wait for the javascript code to be downloaded an executed first
36
+ // and only then that javascript code will programmatically restore the
37
+ // previously-saved scroll position.
38
+ //
39
+ // That would work but it also wouldn't be the most efficient way to do that.
40
+ // Instead, `window.history.scrollRestoration` value could be reset to default beforehand
41
+ // so that when the page finishes refreshing, the web browser could automatically
42
+ // restore the scroll position without waiting for the javascript code to download and run.
43
+ //
44
+ // To reset `window.history.scrollRestoration` value to default beforehand,
45
+ // the code should be notified when the browser tab is about to be terminated or suspended.
46
+ // Terminating could happen for various reasons such as not enough memory, code crash, etc.
47
+ // Suspending is treated equally to terminating because once suspended, it could potentially be
48
+ // terminated afterwards without the code being able to do its stuff while it's suspended.
49
+ //
50
+ // One could consider this feature a minor user experience optimization that relies on the web browser
51
+ // to correctly restore the page scroll every time on page refresh, which it normally does.
52
+ //
53
+ this._sessionExecutionStatusListener = ({
54
+ running
55
+ }) => {
56
+ if (running) {
57
+ (0, _debug.default)('▶ running');
58
+ this._disableAutomaticScrollRestoration();
59
+ } else {
60
+ (0, _debug.default)('⏹ not running');
61
+ this._enableAutomaticScrollRestoration();
62
+
63
+ // There might be previous scroll position already saved in the data storage.
64
+ // Overwrite that previously-saved scroll position with the most up-to-date one
65
+ // just so that there's no stale scroll position left over in the data storage.
66
+ // Alternatively, it could just clear any saved scroll position for this page,
67
+ // since the web browser's automatic scroll restoration is now enabled.
68
+ this._scrollPositionSaver.saveScrollPosition();
69
+ }
70
+ };
71
+ // Overrides the default `window.history.scrollRestoration` value.
72
+ // This prevents the web browser from interfering by disabling its
73
+ // automatic scroll position restoration on "Back"/"Forward" navigation.
74
+ // Instead, the application will have to do it manually.
75
+ // The reason is that when the web browser performs "Back" or "Forward" navigation,
76
+ // it updates the URL in the address bar immediately, and it also attempts to
77
+ // automatically restore scroll position immediately, but the thing is that
78
+ // the application might have delayed rendering of the page due to various reasons
79
+ // such as performance considerations or the architecture of the rendering framework.
80
+ // For example, React framework by design renders pages in "asynchronous" fashion.
81
+ // Hence, by the time the web browser attempts to restore the scroll position,
82
+ // the page might not yet be rendered which would result in incorrect scroll position restoration.
83
+ // That's why the application has to take over this functionality from the web browser.
84
+ this._disableAutomaticScrollRestoration = () => {
85
+ try {
86
+ this._scrollPosition.disableAutomaticScrollRestoration();
87
+ } catch (error) {
88
+ // eslint-disable-next-line no-console
89
+ console.error('[navigation-stack] could not disable default scroll restoration mode');
90
+ }
91
+ };
92
+ this._enableAutomaticScrollRestoration = () => {
93
+ try {
94
+ this._scrollPosition.enableAutomaticScrollRestoration();
95
+ } catch (error) {
96
+ // eslint-disable-next-line no-console
97
+ console.error('[navigation-stack] could not enable default scroll restoration mode');
98
+ }
99
+ };
100
+ this._saveScrollPositionForLocation = (location, scrollableContainerKey, scrollPosition) => {
101
+ this._locationDataStorage.set(location, scrollableContainerKey || _constants.PAGE_SCROLLABLE_CONTAINER_KEY, scrollPosition);
102
+ };
103
+ this._scrollPosition = session.environment.scrollPosition;
104
+ this._sessionLifecycle = session.lifecycle;
105
+ this._locationDataStorage = new _LocationDataStorage.default(session, {
106
+ namespace: 'navigation-stack/scroll-position'
107
+ });
108
+ this._scrollPositionSaver = new _ScrollPositionSaver.default({
109
+ scrollPosition: this._scrollPosition,
110
+ saveScrollPositionForLocation: this._saveScrollPositionForLocation,
111
+ getScrollableContainers: () => this._scrollableContainers,
112
+ getLocation: () => this._location,
113
+ shouldSaveScrollPosition: () => !this._doNotSaveScrollPosition
114
+ });
115
+
116
+ // Originally, `scrollableContainerKey` was auto-generated,
117
+ // but then it didn't work with the concept of dynamically adding or removing
118
+ // scrollable containers after `ScrollPositionRestoration` has already started.
119
+ //
120
+ // this._scrollableContainerKeyCounter = 0;
121
+
122
+ this._scrollableContainers = {};
123
+
124
+ // Add page scrollable container.
125
+ this._scrollableContainers[_constants.PAGE_SCROLLABLE_CONTAINER_KEY] = {
126
+ scrollableContainer: undefined,
127
+ // Using this option, a developer could theoretically provide their own implementation
128
+ // of setting a scroll position. For example, it could use "smooth" (animated) scrolling, etc.
129
+ // This could be part of the public API if anyone provided a sensible real-world use case for it.
130
+ scrollPositionSetter: _options && _options._pageScrollPositionSetter ||
131
+ // The default page scroll position setter.
132
+ new _PageScrollPositionSetter.default(),
133
+ // This function is only used in tests.
134
+ // There seems to be no use of it in real life, hence it's not public API.
135
+ // It's only used in tests.
136
+ _getScrollPositionForLocation: _options && _options._getPageScrollPositionForLocation,
137
+ // This function is only used in tests.
138
+ // There seems to be no use of it in real life, hence it's not public API.
139
+ // It's only used in tests.
140
+ _shouldUpdateScrollPositionForLocation: _options && _options._shouldUpdatePageScrollPositionForLocation
141
+ };
142
+ }
143
+ addScrollableContainer(scrollableContainerKey, scrollableContainer, _options) {
144
+ // Originally, `scrollableContainerKey` was auto-generated,
145
+ // but then it didn't work with the concept of dynamically adding or removing
146
+ // scrollable containers after `ScrollPositionRestoration` has already started.
147
+ //
148
+ // this._scrollableContainerKeyCounter++;
149
+ // const scrollableContainerKey = String(this._scrollableContainerKeyCounter);
150
+
151
+ // Validate `scrollableContainerKey`.
152
+ if (scrollableContainerKey === _constants.PAGE_SCROLLABLE_CONTAINER_KEY) {
153
+ throw new Error(`Scrollable container key "${scrollableContainerKey}" is not allowed`);
154
+ }
155
+
156
+ // Check that it hasn't already been added.
157
+ if (this._scrollableContainers[scrollableContainerKey]) {
158
+ throw new Error(`Scrollable container key "${scrollableContainerKey}" is already added`);
159
+ }
160
+ (0, _debug.default)('add scrollable container', scrollableContainerKey);
161
+
162
+ // Add scrollable container entry.
163
+ this._scrollableContainers[scrollableContainerKey] = {
164
+ // Scrollable container element.
165
+ scrollableContainer,
166
+ // Using this option, a developer could theoretically provide their own implementation
167
+ // of setting a scroll position. For example, it could use "smooth" (animated) scrolling, etc.
168
+ // This could be part of the public API if anyone provided a sensible real-world use case for it.
169
+ scrollPositionSetter: _options && _options._scrollPositionSetter ||
170
+ // The default basic "immediate" scroll position setter.
171
+ new _ScrollPositionSetter.default(),
172
+ // This function is only used in tests.
173
+ // There seems to be no use of it in real life, hence it's not public API.
174
+ // It's only used in tests.
175
+ _shouldUpdateScrollPositionForLocation: _options && _options._shouldUpdateScrollPositionForLocation,
176
+ // This function is only used in tests.
177
+ // There seems to be no use of it in real life, hence it's not public API.
178
+ // It's only used in tests.
179
+ _getScrollPositionForLocation: _options && _options._getScrollPositionForLocation
180
+ };
181
+
182
+ // Scrollable containers could be added at any time, including page mount.
183
+ // For example, a user navigates "Back" to a previous page where there's
184
+ // a "unique" scrollable container that's only present on that page.
185
+ // In that case, the previously-saved scroll position inside the scrollable container
186
+ // should be restored, if it pre-exists. Otherwise, if it doesn't pre-exist,
187
+ // the initial scroll position should be saved for the scrollable container.
188
+ if (this._location) {
189
+ const previouslySavedScrollPosition = this._getSavedScrollPositionForLocation(this._location, scrollableContainerKey);
190
+ if (previouslySavedScrollPosition) {
191
+ (0, _debug.default)('restore scroll position on add scrollable container', this._location.pathname, scrollableContainerKey, previouslySavedScrollPosition);
192
+ this._scrollPosition.setScrollableContainerScrollPosition(scrollableContainer, previouslySavedScrollPosition);
193
+ } else {
194
+ (0, _debug.default)('save scroll position on add scrollable container', this._location.pathname, scrollableContainerKey);
195
+ this._scrollPositionSaver.saveScrollableContainerScrollPosition(scrollableContainerKey, scrollableContainer);
196
+ }
197
+ }
198
+ if (this._started) {
199
+ this._scrollPositionSaver._scrollPositionAutoSaver.addScrollableContainerScrollListener(scrollableContainerKey);
200
+ }
201
+
202
+ // Removes the scrollable container.
203
+ return () => {
204
+ (0, _debug.default)('remove scrollable container', scrollableContainerKey);
205
+ this._scrollPositionSaver._scrollPositionAutoSaver.cancelSaveScrollableContainerScrollPosition(scrollableContainerKey);
206
+ this._scrollPositionSaver._scrollPositionAutoSaver.removeScrollableContainerScrollListener(scrollableContainerKey);
207
+ delete this._scrollableContainers[scrollableContainerKey];
208
+ };
209
+ }
210
+ start() {
211
+ // "Foolproof" check.
212
+ if (this._started) {
213
+ throw new Error('Already started');
214
+ }
215
+ this._started = true;
216
+ this._disableAutomaticScrollRestoration();
217
+ this._scrollPositionSaver.start();
218
+ this._removePageStatusListener = this._sessionLifecycle.addExecutionStatusListener(this._sessionExecutionStatusListener);
219
+ }
220
+
221
+ // This method is "idempotent", i.e. it can be called multiple times.
222
+ stop() {
223
+ // "Foolproof" check.
224
+ if (!this._started) {
225
+ return;
226
+ // throw new Error('Not started');
227
+ }
228
+ this._started = false;
229
+ this._enableAutomaticScrollRestoration();
230
+
231
+ // If there's any scroll position still scheduled to be set, cancel it.
232
+ this._cancelAnyPendingSettingOfScrollPosition();
233
+ this._scrollPositionSaver.stop();
234
+ this._removePageStatusListener();
235
+ }
236
+ _cancelAnyPendingSettingOfScrollPosition() {
237
+ for (const scrollableContainerEntry of Object.values(this._scrollableContainers)) {
238
+ scrollableContainerEntry.scrollPositionSetter.cancel();
239
+ }
240
+ }
241
+ // willRenderLocation = (location) => {
242
+ // // "Foolproof" check.
243
+ // if (!this._started) {
244
+ // throw new Error('`ScrollPositionRestoration` not started');
245
+ // }
246
+ //
247
+ // // For the initial location, it doesn't do anything.
248
+ // if (location.operation === Operations.INIT) {
249
+ // return;
250
+ // }
251
+ //
252
+ // // Since the current page will no longer be rendered,
253
+ // // cancel any scheduled setting of scroll position on it.
254
+ // this._cancelAnyPendingSettingOfScrollPosition();
255
+ //
256
+ // // The previous page may have scheduled an auto-save of scroll position.
257
+ // // Since the previous page is no longer rendered, its scroll position can no longer be obtained,
258
+ // // so any scheduled scroll position auto-save produres are irrelevant now.
259
+ // this._scrollPositionSaver.cancelPreviouslyScheduledAutoSave();
260
+ //
261
+ // // Save the current scroll position on the current page while it's still rendered.
262
+ // // This saved scroll position could later be restored in case of returing to this page.
263
+ // // Even if the current scroll position is a default one (scrolled to top), it should still
264
+ // // be saved in order to overwrite any potential previously-saved non-default scroll position.
265
+ // this._scrollPositionSaver.saveScrollPosition();
266
+ // };
267
+
268
+ // Should be called whenever a different location has been rendered (i.e. immediately after).
269
+ // Returns a Promise that resolves when finished restoring scroll position.
270
+ // There's no need to await for that Promise. It's just there because it exists.
271
+ locationRendered(location) {
272
+ // Validate that `location` has a `key`.
273
+ if (!location.key) {
274
+ throw new Error('`location` must have a `key`');
275
+ }
276
+ (0, _debug.default)('rendered location', location.pathname);
277
+ this._prevLocation = this._location;
278
+ this._location = location;
279
+ this._scrollPosition.init();
280
+ if (!this._started) {
281
+ // `this.start()` requires `this._location` to be set.
282
+ this.start();
283
+
284
+ // The initial page might've been server-side rendered which means that
285
+ // by the time this javascript code is downloaded and executed by the web browser,
286
+ // the user might've already scrolled the page to some position,
287
+ // and all those pre-javascript scroll events won't be registered by `ScrollPositionSaver`.
288
+ // If the user doesn't scroll after javascript is loaded and just navigates to a new page,
289
+ // the initial page won't have any saved scroll position to restore on "Back" navigation.
290
+ // Hence, it should explicitly save the current scroll position at the start of operation.
291
+ if (!this._isDefaultScrollPosition()) {
292
+ // `this._scrollPositionSaver.saveScrollPosition()` requires `this._location` to be set.
293
+ this._scrollPositionSaver.saveScrollPosition();
294
+ }
295
+ }
296
+
297
+ // The previous page may have scheduled an auto-save of scroll position.
298
+ // Since the previous page is no longer rendered, its scroll position can no longer be obtained,
299
+ // so any scheduled scroll position auto-save produres are irrelevant now.
300
+ this._scrollPositionSaver.cancelPreviouslyScheduledAutoSave();
301
+
302
+ // If it was in the middle of setting scroll position for a previous location, cancel it.
303
+ this._cancelAnyPendingSettingOfScrollPosition();
304
+
305
+ // Set the scroll position for the new page:
306
+ // either restore a previously-saved one or set it to a default scroll position.
307
+ return this._setScrollPosition();
308
+ }
309
+
310
+ // Tells if the current scroll position is the default one.
311
+ _isDefaultScrollPosition() {
312
+ for (const scrollableContainerKey of Object.keys(this._scrollableContainers)) {
313
+ if (scrollableContainerKey === _constants.PAGE_SCROLLABLE_CONTAINER_KEY) {
314
+ if (!areEqualScrollPositions(this._scrollPosition.getPageScrollPosition(), this._getDefaultScrollPosition())) {
315
+ return false;
316
+ }
317
+ } else {
318
+ const scrollableContainerEntry = this._scrollableContainers[scrollableContainerKey];
319
+ if (!areEqualScrollPositions(this._scrollPosition.getScrollableContainerScrollPosition(scrollableContainerEntry.scrollableContainer), this._getDefaultScrollPosition())) {
320
+ return false;
321
+ }
322
+ }
323
+ }
324
+ return true;
325
+ }
326
+
327
+ // Restores scroll position.
328
+ // Returns a Promise that resolves when finished setting scroll position.
329
+ // There's no need to await for this Promise. It just exists.
330
+ _setScrollPosition() {
331
+ return Promise.all(Object.keys(this._scrollableContainers).map(scrollableContainerKey => {
332
+ const scrollableContainerEntry = this._scrollableContainers[scrollableContainerKey];
333
+
334
+ // This function is only used in tests.
335
+ // There seems to be no use of it in real life, hence it's not public API.
336
+ // It's only used in tests.
337
+ if (scrollableContainerEntry._shouldUpdateScrollPositionForLocation) {
338
+ if (!scrollableContainerEntry._shouldUpdateScrollPositionForLocation(this._location, this._prevLocation)) {
339
+ return Promise.resolve();
340
+ }
341
+ }
342
+
343
+ // Scroll position (or anchor) to set.
344
+ let scrollPositionOrAnchorToSet;
345
+
346
+ // This function is only used in tests.
347
+ // There seems to be no use of it in real life, hence it's not public API.
348
+ // It's only used in tests.
349
+ if (scrollableContainerEntry._getScrollPositionForLocation) {
350
+ scrollPositionOrAnchorToSet = scrollableContainerEntry._getScrollPositionForLocation(this._location, this._prevLocation);
351
+ }
352
+
353
+ // Get scroll position (or anchor) to set.
354
+ if (!scrollPositionOrAnchorToSet) {
355
+ scrollPositionOrAnchorToSet = scrollableContainerKey === _constants.PAGE_SCROLLABLE_CONTAINER_KEY ? this._getPageScrollPositionOrAnchorToSet(this._location) : this._getScrollableContainerScrollPositionToSet(this._location, scrollableContainerKey);
356
+ }
357
+ (0, _debug.default)('restore scroll position', this._location.pathname, scrollableContainerKey, scrollPositionOrAnchorToSet);
358
+
359
+ // Set scroll position of scrollable container.
360
+ return scrollableContainerEntry.scrollPositionSetter.set(scrollableContainerEntry.scrollableContainer, scrollPositionOrAnchorToSet, this._scrollPosition);
361
+ }));
362
+ }
363
+ _getSavedScrollPositionForLocation(location, scrollableContainerKey = _constants.PAGE_SCROLLABLE_CONTAINER_KEY) {
364
+ return this._locationDataStorage.get(location, scrollableContainerKey);
365
+ }
366
+ // Returns scroll position coordinates or an anchor name.
367
+ _getPageScrollPositionOrAnchorToSet(location) {
368
+ // If it's a return to a previously-visited location,
369
+ // read the saved scroll position from session data store.
370
+ return this._getSavedScrollPositionForLocation(location) || this._getAnchor(location) || this._getDefaultScrollPosition();
371
+ }
372
+
373
+ // Returns scroll position coordinates.
374
+ _getScrollableContainerScrollPositionToSet(location, scrollableContainerKey) {
375
+ // If it's a return to a previously-visited location,
376
+ // read the saved scroll position from session data store.
377
+ return this._getSavedScrollPositionForLocation(location, scrollableContainerKey) || this._getDefaultScrollPosition();
378
+ }
379
+ _getAnchor(location) {
380
+ const {
381
+ hash
382
+ } = location;
383
+ if (hash && hash !== '#') {
384
+ return hash.slice('#'.length);
385
+ }
386
+ return undefined;
387
+ }
388
+ _getDefaultScrollPosition() {
389
+ return [0, 0];
390
+ }
391
+
392
+ // `_enableSavingScrollPosition()` and `_disableSavingScrollPosition()`
393
+ // aren't used in real life and are not part of the public API.
394
+ // They're only used in tests.
395
+ _enableSavingScrollPosition() {
396
+ this._doNotSaveScrollPosition = undefined;
397
+ }
398
+
399
+ // `_enableSavingScrollPosition()` and `_disableSavingScrollPosition()`
400
+ // aren't used in real life and are not part of the public API.
401
+ // They're only used in tests.
402
+ _disableSavingScrollPosition() {
403
+ this._doNotSaveScrollPosition = true;
404
+ }
405
+ }
406
+ exports.default = ScrollPositionRestoration;
407
+ module.exports = exports.default;