navigation-stack 0.3.0 → 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.
Files changed (253) hide show
  1. package/README.md +603 -163
  2. package/data-storage/package.json +6 -0
  3. package/karma.conf.cjs +21 -4
  4. package/lib/cjs/NavigationStack.js +73 -0
  5. package/lib/cjs/data-storage/DataStorage.js +71 -0
  6. package/lib/cjs/data-storage/LocationDataStorage.js +29 -0
  7. package/lib/cjs/data-storage/index.js +9 -0
  8. package/lib/cjs/environment/InMemoryEnvironment.js +15 -0
  9. package/lib/cjs/environment/WebBrowserEnvironment.js +15 -0
  10. package/lib/cjs/environment/data-storage/InMemoryDataStorage.js +27 -0
  11. package/lib/cjs/environment/data-storage/WebBrowserDataStorage.js +21 -0
  12. package/lib/cjs/environment/scroll-position/InMemoryScrollPosition.js +44 -0
  13. package/lib/cjs/environment/scroll-position/WebBrowserScrollPosition.js +60 -0
  14. package/lib/cjs/getLocationFromInternalLocation.js +14 -0
  15. package/lib/cjs/index.js +20 -16
  16. package/lib/cjs/navigationBlockers.js +25 -23
  17. package/lib/cjs/{normalizeInputLocation.js → parseInputLocation.js} +25 -9
  18. package/lib/cjs/{ActionTypes.js → redux/ActionTypes.js} +1 -1
  19. package/lib/cjs/redux/ActionTypesInternal.js +8 -0
  20. package/lib/cjs/{Actions.js → redux/Actions.js} +5 -4
  21. package/lib/cjs/redux/createMiddlewares.js +60 -0
  22. package/lib/cjs/redux/index.js +13 -0
  23. package/lib/cjs/redux/internalLocationReducer.js +14 -0
  24. package/lib/cjs/redux/middleware/createAddInputLocationBasePathMiddleware.js +32 -0
  25. package/lib/cjs/redux/middleware/createNonProgrammaticNavigationBlockerMiddleware.js +113 -0
  26. package/lib/cjs/redux/middleware/createProgrammaticNavigationBlockerMiddleware.js +94 -0
  27. package/lib/cjs/redux/middleware/createRemoveOutputLocationBasePathMiddleware.js +30 -0
  28. package/lib/cjs/redux/middleware/createUpdateInternalLocationMiddleware.js +73 -0
  29. package/lib/cjs/{middleware/navigationActionMiddleware.js → redux/middleware/navigationOperationMiddleware.js} +11 -8
  30. package/lib/cjs/{middleware/normalizeInputLocationMiddleware.js → redux/middleware/parseInputLocationMiddleware.js} +6 -4
  31. package/lib/cjs/redux/middleware/updateLocationMiddleware.js +34 -0
  32. package/lib/cjs/scroll-position/PageScrollPositionSetter.js +97 -0
  33. package/lib/cjs/scroll-position/ScrollPositionAutoSaver.js +130 -0
  34. package/lib/cjs/scroll-position/ScrollPositionRestoration.js +383 -0
  35. package/lib/cjs/scroll-position/ScrollPositionSaver.js +81 -0
  36. package/lib/cjs/scroll-position/ScrollPositionSetter.js +16 -0
  37. package/lib/cjs/scroll-position/constants.js +5 -0
  38. package/lib/cjs/scroll-position/index.js +7 -0
  39. package/lib/cjs/scroll-position/scheduleNextTick.js +11 -0
  40. package/lib/cjs/session/InMemorySession.js +22 -0
  41. package/lib/cjs/session/ServerSideRenderSession.js +17 -0
  42. package/lib/cjs/session/Session.js +196 -0
  43. package/lib/cjs/session/WebBrowserSession.js +20 -0
  44. package/lib/cjs/session/key/createSessionKey.js +23 -0
  45. package/lib/cjs/session/lifecycle/InMemorySessionLifecycle.js +19 -0
  46. package/lib/cjs/session/lifecycle/WebBrowserSessionLifecycle.js +128 -0
  47. package/lib/cjs/session/lifecycle/page-lifecycle/PageLifecycle.js +269 -0
  48. package/lib/cjs/session/lifecycle/page-lifecycle/PageLifecycleInstance.js +8 -0
  49. package/lib/cjs/session/lifecycle/page-lifecycle/supportsConstructableEventTarget.js +33 -0
  50. package/lib/cjs/session/navigation/InMemoryNavigation.js +104 -0
  51. package/lib/cjs/session/navigation/ServerSideNavigation.js +61 -0
  52. package/lib/cjs/session/navigation/WebBrowserNavigation.js +221 -0
  53. package/lib/cjs/session/navigation/error/NavigationOutOfBoundsError.js +12 -0
  54. package/lib/cjs/session/navigation/error/ServerSideNavigationError.js +21 -0
  55. package/lib/cjs/session/navigation/operation/operations.js +11 -0
  56. package/lib/cjs/session/subscription/Subscription.js +81 -0
  57. package/lib/data-storage/index.d.ts +35 -0
  58. package/lib/esm/NavigationStack.js +66 -0
  59. package/lib/esm/data-storage/DataStorage.js +65 -0
  60. package/lib/esm/data-storage/LocationDataStorage.js +22 -0
  61. package/lib/esm/data-storage/index.js +2 -0
  62. package/lib/esm/environment/InMemoryEnvironment.js +8 -0
  63. package/lib/esm/environment/WebBrowserEnvironment.js +8 -0
  64. package/lib/esm/environment/data-storage/InMemoryDataStorage.js +21 -0
  65. package/lib/esm/environment/data-storage/WebBrowserDataStorage.js +15 -0
  66. package/lib/esm/environment/scroll-position/InMemoryScrollPosition.js +38 -0
  67. package/lib/esm/environment/scroll-position/WebBrowserScrollPosition.js +54 -0
  68. package/lib/esm/getLocationFromInternalLocation.js +9 -0
  69. package/lib/esm/index.js +10 -8
  70. package/lib/esm/navigationBlockers.js +25 -23
  71. package/lib/esm/{normalizeInputLocation.js → parseInputLocation.js} +24 -8
  72. package/lib/esm/{ActionTypes.js → redux/ActionTypes.js} +1 -1
  73. package/lib/esm/redux/ActionTypesInternal.js +3 -0
  74. package/lib/esm/{Actions.js → redux/Actions.js} +5 -4
  75. package/lib/esm/redux/createMiddlewares.js +54 -0
  76. package/lib/esm/redux/index.js +4 -0
  77. package/lib/esm/redux/internalLocationReducer.js +8 -0
  78. package/lib/esm/redux/middleware/createAddInputLocationBasePathMiddleware.js +27 -0
  79. package/lib/esm/redux/middleware/createNonProgrammaticNavigationBlockerMiddleware.js +108 -0
  80. package/lib/esm/redux/middleware/createProgrammaticNavigationBlockerMiddleware.js +88 -0
  81. package/lib/esm/redux/middleware/createRemoveOutputLocationBasePathMiddleware.js +25 -0
  82. package/lib/esm/redux/middleware/createUpdateInternalLocationMiddleware.js +68 -0
  83. package/lib/esm/{middleware/navigationActionMiddleware.js → redux/middleware/navigationOperationMiddleware.js} +10 -7
  84. package/lib/esm/{middleware/normalizeInputLocationMiddleware.js → redux/middleware/parseInputLocationMiddleware.js} +5 -3
  85. package/lib/esm/redux/middleware/updateLocationMiddleware.js +28 -0
  86. package/lib/esm/scroll-position/PageScrollPositionSetter.js +91 -0
  87. package/lib/esm/scroll-position/ScrollPositionAutoSaver.js +123 -0
  88. package/lib/esm/scroll-position/ScrollPositionRestoration.js +376 -0
  89. package/lib/esm/scroll-position/ScrollPositionSaver.js +74 -0
  90. package/lib/esm/scroll-position/ScrollPositionSetter.js +10 -0
  91. package/lib/esm/scroll-position/constants.js +1 -0
  92. package/lib/esm/scroll-position/index.js +1 -0
  93. package/lib/esm/scroll-position/scheduleNextTick.js +6 -0
  94. package/lib/esm/session/InMemorySession.js +15 -0
  95. package/lib/esm/session/ServerSideRenderSession.js +11 -0
  96. package/lib/esm/session/Session.js +189 -0
  97. package/lib/esm/session/WebBrowserSession.js +13 -0
  98. package/lib/esm/session/key/createSessionKey.js +18 -0
  99. package/lib/esm/session/lifecycle/InMemorySessionLifecycle.js +13 -0
  100. package/lib/esm/session/lifecycle/WebBrowserSessionLifecycle.js +120 -0
  101. package/lib/esm/session/lifecycle/page-lifecycle/PageLifecycle.js +263 -0
  102. package/lib/esm/session/lifecycle/page-lifecycle/PageLifecycleInstance.js +2 -0
  103. package/lib/esm/session/lifecycle/page-lifecycle/supportsConstructableEventTarget.js +30 -0
  104. package/lib/esm/session/navigation/InMemoryNavigation.js +97 -0
  105. package/lib/esm/session/navigation/ServerSideNavigation.js +54 -0
  106. package/lib/esm/session/navigation/WebBrowserNavigation.js +213 -0
  107. package/lib/esm/session/navigation/error/NavigationOutOfBoundsError.js +6 -0
  108. package/lib/esm/session/navigation/error/ServerSideNavigationError.js +14 -0
  109. package/lib/esm/session/navigation/operation/operations.js +6 -0
  110. package/lib/esm/session/subscription/Subscription.js +75 -0
  111. package/lib/index.d.ts +178 -157
  112. package/lib/redux/index.d.ts +90 -0
  113. package/lib/scroll-position/index.d.ts +107 -0
  114. package/package.json +9 -5
  115. package/redux/package.json +6 -0
  116. package/scroll-position/package.json +6 -0
  117. package/src/NavigationStack.js +84 -0
  118. package/src/data-storage/DataStorage.js +69 -0
  119. package/src/data-storage/LocationDataStorage.js +23 -0
  120. package/src/data-storage/index.js +2 -0
  121. package/src/environment/InMemoryEnvironment.js +9 -0
  122. package/src/environment/WebBrowserEnvironment.js +9 -0
  123. package/src/environment/data-storage/InMemoryDataStorage.js +23 -0
  124. package/src/environment/data-storage/WebBrowserDataStorage.js +17 -0
  125. package/src/environment/scroll-position/InMemoryScrollPosition.js +45 -0
  126. package/src/environment/scroll-position/WebBrowserScrollPosition.js +72 -0
  127. package/src/getLocationFromInternalLocation.js +7 -0
  128. package/src/index.js +10 -8
  129. package/src/navigationBlockers.js +28 -27
  130. package/src/{normalizeInputLocation.js → parseInputLocation.js} +23 -8
  131. package/src/{ActionTypes.js → redux/ActionTypes.js} +1 -1
  132. package/src/redux/ActionTypesInternal.js +3 -0
  133. package/src/{Actions.js → redux/Actions.js} +4 -3
  134. package/src/redux/createMiddlewares.js +65 -0
  135. package/src/redux/index.js +4 -0
  136. package/src/redux/internalLocationReducer.js +9 -0
  137. package/src/redux/middleware/createAddInputLocationBasePathMiddleware.js +27 -0
  138. package/src/redux/middleware/createNonProgrammaticNavigationBlockerMiddleware.js +119 -0
  139. package/src/redux/middleware/createProgrammaticNavigationBlockerMiddleware.js +94 -0
  140. package/src/redux/middleware/createRemoveOutputLocationBasePathMiddleware.js +26 -0
  141. package/src/redux/middleware/createUpdateInternalLocationMiddleware.js +72 -0
  142. package/src/{middleware/navigationActionMiddleware.js → redux/middleware/navigationOperationMiddleware.js} +10 -3
  143. package/src/{middleware/normalizeInputLocationMiddleware.js → redux/middleware/parseInputLocationMiddleware.js} +5 -3
  144. package/src/redux/middleware/updateLocationMiddleware.js +28 -0
  145. package/src/scroll-position/PageScrollPositionSetter.js +110 -0
  146. package/src/scroll-position/ScrollPositionAutoSaver.js +151 -0
  147. package/src/scroll-position/ScrollPositionRestoration.js +506 -0
  148. package/src/scroll-position/ScrollPositionSaver.js +100 -0
  149. package/src/scroll-position/ScrollPositionSetter.js +16 -0
  150. package/src/scroll-position/constants.js +1 -0
  151. package/src/scroll-position/index.js +1 -0
  152. package/src/scroll-position/scheduleNextTick.js +6 -0
  153. package/src/session/InMemorySession.js +13 -0
  154. package/src/session/ServerSideRenderSession.js +9 -0
  155. package/src/session/Session.js +216 -0
  156. package/src/session/WebBrowserSession.js +13 -0
  157. package/src/session/key/createSessionKey.js +18 -0
  158. package/src/session/lifecycle/InMemorySessionLifecycle.js +13 -0
  159. package/src/session/lifecycle/WebBrowserSessionLifecycle.js +126 -0
  160. package/src/session/lifecycle/page-lifecycle/PageLifecycle.js +291 -0
  161. package/src/session/lifecycle/page-lifecycle/PageLifecycleInstance.js +3 -0
  162. package/src/session/lifecycle/page-lifecycle/supportsConstructableEventTarget.js +32 -0
  163. package/src/session/navigation/InMemoryNavigation.js +78 -0
  164. package/src/session/navigation/ServerSideNavigation.js +43 -0
  165. package/src/session/navigation/WebBrowserNavigation.js +224 -0
  166. package/src/session/navigation/error/NavigationOutOfBoundsError.js +7 -0
  167. package/src/session/navigation/error/ServerSideNavigationError.js +18 -0
  168. package/src/session/navigation/operation/operations.js +6 -0
  169. package/src/session/subscription/Subscription.js +76 -0
  170. package/test/NavigationStack.test.js +296 -0
  171. package/test/{LocationDataStorage.test.js → data-storage/LocationDataStorage.test.js} +3 -3
  172. package/test/data-storage/index.test.js +8 -0
  173. package/test/index.js +12 -0
  174. package/test/index.test.js +8 -7
  175. package/test/{helpers.js → middlewareTestUtil.js} +9 -12
  176. package/test/{normalizeInputLocation.test.js → parseInputLocationMiddleware.test.js} +9 -9
  177. package/test/{Action.test.js → redux/Action.test.js} +7 -6
  178. package/test/{ActionTypes.test.js → redux/ActionTypes.test.js} +2 -2
  179. package/test/redux/createMiddlewares.test.js +96 -0
  180. package/test/redux/index.test.js +10 -0
  181. package/test/{locationReducer.test.js → redux/locationReducer.test.js} +4 -7
  182. package/test/redux/middleware/createAddInputLocationBasePathMiddleware.test.js +40 -0
  183. package/test/redux/middleware/createNonProgrammaticNavigationBlockerMiddleware.test.js +264 -0
  184. package/test/redux/middleware/createProgrammaticNavigationBlockerMiddleware.test.js +312 -0
  185. package/test/redux/middleware/createRemoveOutputLocationBasePathMiddleware.test.js +51 -0
  186. package/test/{middleware/navigationActionMiddleware.test.js → redux/middleware/navigationOperationMiddleware.test.js} +16 -12
  187. package/test/{middleware/normalizeInputLocationMiddleware.test.js → redux/middleware/parseInputLocationMiddleware.test.js} +4 -4
  188. package/test/scroll-position/ScrollPositionRestoration.test.js +418 -0
  189. package/test/scroll-position/addScrollableContainer.js +36 -0
  190. package/test/scroll-position/addScrollableContainerWithHyperlink.js +50 -0
  191. package/test/scroll-position/createApp.js +112 -0
  192. package/test/scroll-position/delay.js +9 -0
  193. package/test/scroll-position/mockPageLifecycle.js +17 -0
  194. package/test/scroll-position/runApp.js +24 -0
  195. package/test/scroll-position/withScrollableContainerAtIndexPage.js +62 -0
  196. package/test/session/InMemorySession.test.js +348 -0
  197. package/test/session/ServerSession.test.js +17 -9
  198. package/test/session/WebBrowserSession.test.js +265 -0
  199. package/test/testUtil.js +3 -0
  200. package/types/data-storage/index.d.ts +35 -0
  201. package/types/index.d.ts +178 -157
  202. package/types/redux/index.d.ts +90 -0
  203. package/types/scroll-position/index.d.ts +107 -0
  204. package/types/tsconfig.json +1 -1
  205. package/lib/cjs/LocationDataStorage.js +0 -60
  206. package/lib/cjs/addBeforeLocationChangeListener.js +0 -7
  207. package/lib/cjs/beforeLocationChangeListeners.js +0 -51
  208. package/lib/cjs/createMiddlewares.js +0 -47
  209. package/lib/cjs/middleware/createBasePathMiddleware.js +0 -24
  210. package/lib/cjs/middleware/createBeforeLocationChangeListenerMiddleware.js +0 -39
  211. package/lib/cjs/middleware/createLocationMiddleware.js +0 -56
  212. package/lib/cjs/middleware/createNavigationBlockerMiddleware.js +0 -161
  213. package/lib/cjs/middleware/createTransformLocationMiddleware.js +0 -38
  214. package/lib/cjs/onlyAllowedOnClientSide.js +0 -10
  215. package/lib/cjs/session/BrowserSession.js +0 -235
  216. package/lib/cjs/session/MemorySession.js +0 -223
  217. package/lib/cjs/session/ServerSession.js +0 -65
  218. package/lib/esm/LocationDataStorage.js +0 -53
  219. package/lib/esm/addBeforeLocationChangeListener.js +0 -2
  220. package/lib/esm/beforeLocationChangeListeners.js +0 -44
  221. package/lib/esm/createMiddlewares.js +0 -41
  222. package/lib/esm/middleware/createBasePathMiddleware.js +0 -19
  223. package/lib/esm/middleware/createBeforeLocationChangeListenerMiddleware.js +0 -34
  224. package/lib/esm/middleware/createLocationMiddleware.js +0 -50
  225. package/lib/esm/middleware/createNavigationBlockerMiddleware.js +0 -156
  226. package/lib/esm/middleware/createTransformLocationMiddleware.js +0 -33
  227. package/lib/esm/onlyAllowedOnClientSide.js +0 -5
  228. package/lib/esm/session/BrowserSession.js +0 -229
  229. package/lib/esm/session/MemorySession.js +0 -217
  230. package/lib/esm/session/ServerSession.js +0 -58
  231. package/src/LocationDataStorage.js +0 -59
  232. package/src/addBeforeLocationChangeListener.js +0 -2
  233. package/src/beforeLocationChangeListeners.js +0 -54
  234. package/src/createMiddlewares.js +0 -45
  235. package/src/middleware/createBasePathMiddleware.js +0 -20
  236. package/src/middleware/createBeforeLocationChangeListenerMiddleware.js +0 -40
  237. package/src/middleware/createLocationMiddleware.js +0 -55
  238. package/src/middleware/createNavigationBlockerMiddleware.js +0 -168
  239. package/src/middleware/createTransformLocationMiddleware.js +0 -29
  240. package/src/onlyAllowedOnClientSide.js +0 -5
  241. package/src/session/BrowserSession.js +0 -235
  242. package/src/session/MemorySession.js +0 -219
  243. package/src/session/ServerSession.js +0 -67
  244. package/test/createMiddlewares.test.js +0 -62
  245. package/test/middleware/createBasePathMiddleware.test.js +0 -67
  246. package/test/middleware/createBeforeLocationChangeListenerMiddleware.test.js +0 -141
  247. package/test/middleware/createNavigationBlockerMiddleware.test.js +0 -471
  248. package/test/middleware/createTransformLocationMiddleware.test.js +0 -44
  249. package/test/session/BrowserSession.test.js +0 -182
  250. package/test/session/MemorySession.test.js +0 -244
  251. /package/lib/cjs/{locationReducer.js → redux/locationReducer.js} +0 -0
  252. /package/lib/esm/{locationReducer.js → redux/locationReducer.js} +0 -0
  253. /package/src/{locationReducer.js → redux/locationReducer.js} +0 -0
@@ -0,0 +1,100 @@
1
+ /* eslint-disable no-underscore-dangle */
2
+
3
+ import ScrollPositionAutoSaver from './ScrollPositionAutoSaver';
4
+ import { PAGE_SCROLLABLE_CONTAINER_KEY } from './constants';
5
+
6
+ export default class ScrollPositionSaver {
7
+ constructor({
8
+ scrollPosition,
9
+ getLocation,
10
+ saveScrollPositionForLocation,
11
+ getScrollableContainers,
12
+ shouldSaveScrollPosition,
13
+ }) {
14
+ this._scrollPosition = scrollPosition;
15
+ this._getLocation = getLocation;
16
+ this._saveScrollPositionForLocation = saveScrollPositionForLocation;
17
+ this._getScrollableContainers = getScrollableContainers;
18
+ this._shouldSaveScrollPosition = shouldSaveScrollPosition;
19
+
20
+ this._scrollPositionAutoSaver = new ScrollPositionAutoSaver({
21
+ scrollPosition: this._scrollPosition,
22
+ scrollPositionSaver: this,
23
+ getScrollableContainers,
24
+ shouldSaveScrollPosition,
25
+ });
26
+ }
27
+
28
+ start() {
29
+ this._scrollPositionAutoSaver.start();
30
+ }
31
+
32
+ stop() {
33
+ this._scrollPositionAutoSaver.stop();
34
+ }
35
+
36
+ cancelPreviouslyScheduledAutoSave() {
37
+ this._scrollPositionAutoSaver.cancelScheduledAutoSave();
38
+ }
39
+
40
+ saveScrollPosition() {
41
+ // This flag is not used in real life and is only used in tests (for some reason).
42
+ if (!this._shouldSaveScrollPosition()) {
43
+ return;
44
+ }
45
+
46
+ // Get scrollable containers.
47
+ const scrollableContainers = this._getScrollableContainers();
48
+
49
+ // Save scroll position of each scrollable container.
50
+ for (const scrollableContainerKey of Object.keys(scrollableContainers)) {
51
+ if (scrollableContainerKey === PAGE_SCROLLABLE_CONTAINER_KEY) {
52
+ this.savePageScrollPosition();
53
+ } else {
54
+ this.saveScrollableContainerScrollPosition(
55
+ scrollableContainerKey,
56
+ scrollableContainers[scrollableContainerKey].scrollableContainer,
57
+ );
58
+ }
59
+ }
60
+ }
61
+
62
+ savePageScrollPosition() {
63
+ // * If this is not a scheduled "auto-save" of scroll position
64
+ // and there already exists any scheduled "auto-save" of scroll position,
65
+ // cancel it and save scroll position right now instead.
66
+ // * If this is a scheduled "auto-save" of scroll position,
67
+ // clear the "cancel" function because it's no longer of use.
68
+ this._scrollPositionAutoSaver.cancelSavePageScrollPosition();
69
+
70
+ // Save scroll position.
71
+ this._saveScrollPositionForLocation(
72
+ this._getLocation(),
73
+ undefined,
74
+ this._scrollPosition.getPageScrollPosition(),
75
+ );
76
+ }
77
+
78
+ saveScrollableContainerScrollPosition(
79
+ scrollableContainerKey,
80
+ scrollableContainer,
81
+ ) {
82
+ // * If this is not a scheduled "auto-save" of scroll position
83
+ // and there already exists any scheduled "auto-save" of scroll position,
84
+ // cancel it and save scroll position right now instead.
85
+ // * If this is a scheduled "auto-save" of scroll position,
86
+ // clear the "cancel" function because it's no longer of use.
87
+ this._scrollPositionAutoSaver.cancelSaveScrollableContainerScrollPosition(
88
+ scrollableContainerKey,
89
+ );
90
+
91
+ // Save scroll position.
92
+ this._saveScrollPositionForLocation(
93
+ this._getLocation(),
94
+ scrollableContainerKey,
95
+ this._scrollPosition.getScrollableContainerScrollPosition(
96
+ scrollableContainer,
97
+ ),
98
+ );
99
+ }
100
+ }
@@ -0,0 +1,16 @@
1
+ export default class ScrollPositionSetter {
2
+ set(scrollableContainer, scrollPositionOrAnchor, environmentScrollPosition) {
3
+ if (typeof scrollPositionOrAnchor === 'string') {
4
+ throw new Error(
5
+ '`ScrollPositionSetter` only allows setting numeric scroll position, not an anchor string',
6
+ );
7
+ }
8
+ environmentScrollPosition.setScrollableContainerScrollPosition(
9
+ scrollableContainer,
10
+ scrollPositionOrAnchor,
11
+ );
12
+ return Promise.resolve();
13
+ }
14
+
15
+ cancel() {}
16
+ }
@@ -0,0 +1 @@
1
+ export const PAGE_SCROLLABLE_CONTAINER_KEY = 'page';
@@ -0,0 +1 @@
1
+ export ScrollPositionRestoration from './ScrollPositionRestoration';
@@ -0,0 +1,6 @@
1
+ export default function scheduleNextTick(func) {
2
+ const timerId = window.requestAnimationFrame(func);
3
+ return () => {
4
+ cancelAnimationFrame(timerId);
5
+ };
6
+ }
@@ -0,0 +1,13 @@
1
+ import Session from './Session';
2
+ import InMemoryEnvironment from '../environment/InMemoryEnvironment';
3
+ import InMemorySessionLifecycle from './lifecycle/InMemorySessionLifecycle';
4
+ import InMemoryNavigation from './navigation/InMemoryNavigation';
5
+
6
+ export default class InMemorySession extends Session {
7
+ constructor({ navigation = new InMemoryNavigation() } = {}) {
8
+ super({ navigation });
9
+
10
+ this.environment = new InMemoryEnvironment();
11
+ this.lifecycle = new InMemorySessionLifecycle();
12
+ }
13
+ }
@@ -0,0 +1,9 @@
1
+ import InMemorySession from './InMemorySession';
2
+ import ServerSideNavigation from './navigation/ServerSideNavigation';
3
+
4
+ // `ServerSideRenderSession` is just a `InMemorySession` that specifically prohibits any navigation.
5
+ export default class ServerSideRenderSession extends InMemorySession {
6
+ constructor() {
7
+ super({ navigation: new ServerSideNavigation() });
8
+ }
9
+ }
@@ -0,0 +1,216 @@
1
+ import parseInputLocation from '../parseInputLocation';
2
+ import createSessionKey from './key/createSessionKey';
3
+ import NavigationOutOfBoundsError from './navigation/error/NavigationOutOfBoundsError';
4
+ import NavigationOperations from './navigation/operation/operations';
5
+ import Subscription from './subscription/Subscription';
6
+
7
+ const INITIAL_KEY_INDEX = -1;
8
+ const INITIAL_INDEX = -1;
9
+
10
+ const INIT_LOCATION_DELTA = 0;
11
+
12
+ export default class Session {
13
+ constructor({ navigation }) {
14
+ // `key` is used in `WebBrowserSession` to uniquely identify a session
15
+ // when storing data in a `WebBrowserDataStorage` which uses `window.sessionStorage`
16
+ // under the hood, and `window.sessionStorage` is shared between different sessions.
17
+ this.key = createSessionKey();
18
+
19
+ // `this._locationKeyIndex` is incremented every time the current location changes.
20
+ this._locationKeyIndex = INITIAL_KEY_INDEX;
21
+
22
+ // `this._currentLocationIndex` is the index of the top element in the navigation stack.
23
+ // I.e. it's the index of the "current" location in the navigation stack.
24
+ this._currentLocationIndex = INITIAL_INDEX;
25
+
26
+ // The `index` of the terminal (rightmost) location in the navigation history.
27
+ // In other words, this is the last location index that it can `.shift()` to.
28
+ this._terminalLocationIndex = this._currentLocationIndex;
29
+
30
+ // Create `navigation`.
31
+ this._navigation = navigation;
32
+
33
+ // Manages subscriptions.
34
+ this._subscription = new Subscription({
35
+ activateSubscription: (listener) => {
36
+ return this._navigation.subscribe(listener);
37
+ },
38
+ });
39
+
40
+ // Update current location index when a location change was not initiated
41
+ // by this session but rather by the user clicking "Back" or "Forward" button.
42
+ this._unsubscribe = this.subscribe((location) => {
43
+ // Update `this._currentLocationIndex` when the location change was not initiated
44
+ // by this session but rather by the user clicking "Back" or "Forward" button.
45
+ this._currentLocationIndex = location.index;
46
+ // Since `currentLocationIndex` has been updated, update `terminalLocationIndex`.
47
+ // It's not really currently possible to see a "PUSH" or a "REPLACE" operation here,
48
+ // but if it was possible, this call would be required. It would also be required
49
+ // by `navigation` to call `session.getNextKey()` function to increment `locationKeyIndex`.
50
+ this._updateTerminalLocationIndex(location);
51
+ });
52
+ }
53
+
54
+ // Subscribes to changes in location.
55
+ subscribe(listener) {
56
+ return this._subscription.subscribe((location) => {
57
+ if (
58
+ !this._isStarted() &&
59
+ location.operation !== NavigationOperations.INIT
60
+ ) {
61
+ // eslint-disable-next-line no-console
62
+ console.error('Unexpected location change', location);
63
+ throw new Error('Not started');
64
+ } else {
65
+ // Call the listener.
66
+ listener(location);
67
+ }
68
+ });
69
+ }
70
+
71
+ start(initialLocation) {
72
+ if (this._stopped) {
73
+ throw new Error('Can not be restarted');
74
+ }
75
+
76
+ // Simplify "developer experience" by automatically calling
77
+ // `.init(initialLocation)` in case of using a `WebBrowserSession`.
78
+ //
79
+ // That's because `WebBrowserSession` environment already knows
80
+ // the initial location by the time javascript code starts execution.
81
+ //
82
+ if (!initialLocation) {
83
+ initialLocation = this._navigation.getInitialLocation();
84
+ if (initialLocation) {
85
+ initialLocation = parseInputLocation(initialLocation);
86
+ }
87
+ }
88
+
89
+ if (!initialLocation) {
90
+ throw new Error('`initialLocation` is required');
91
+ }
92
+
93
+ if (this._currentLocationIndex !== INITIAL_INDEX) {
94
+ throw new Error('Already started');
95
+ }
96
+
97
+ this._started = true;
98
+
99
+ const key = this._getNextLocationKey();
100
+ const index = INITIAL_INDEX + 1;
101
+ const delta = INIT_LOCATION_DELTA;
102
+
103
+ const locationResult = this._navigation.init(initialLocation, {
104
+ operation: NavigationOperations.INIT,
105
+ key,
106
+ index,
107
+ delta,
108
+ });
109
+
110
+ if (locationResult) {
111
+ this._subscription.notifySubscribers(locationResult);
112
+ }
113
+ }
114
+
115
+ stop() {
116
+ if (this._stopped) {
117
+ throw Error('Already stopped');
118
+ }
119
+
120
+ // Once stopped, it won't be able to be restarted.
121
+ this._stopped = true;
122
+
123
+ // Remove location change subscription.
124
+ this._unsubscribe();
125
+
126
+ // Even if it calls `unsubscribe()` function above, any other subscriptions
127
+ // would still stay. For example, subscriptions created by the application code.
128
+ // To work around that, `.stop()` function removes all subscriptions.
129
+ this._subscription.stop();
130
+ }
131
+
132
+ navigate(operation, location) {
133
+ if (!this._isStarted()) {
134
+ throw Error('Not started');
135
+ }
136
+
137
+ if (
138
+ operation !== NavigationOperations.PUSH &&
139
+ operation !== NavigationOperations.REPLACE
140
+ ) {
141
+ throw Error(`Unknown navigation operation: ${operation}`);
142
+ }
143
+
144
+ const delta = operation === NavigationOperations.PUSH ? 1 : 0;
145
+
146
+ this._updateTerminalLocationIndex({ operation });
147
+
148
+ const key = this._getNextLocationKey();
149
+ const index = this._currentLocationIndex + delta;
150
+
151
+ // Navigate to the location.
152
+ const locationResult = this._navigation.navigate(location, {
153
+ operation,
154
+ key,
155
+ index,
156
+ delta,
157
+ });
158
+
159
+ if (locationResult) {
160
+ this._subscription.notifySubscribers(locationResult);
161
+ }
162
+ }
163
+
164
+ shift(delta) {
165
+ if (!this._isStarted()) {
166
+ throw Error('Not started');
167
+ }
168
+
169
+ // If there'll be no navigation, return.
170
+ if (delta === 0) {
171
+ return;
172
+ }
173
+
174
+ const index = this._currentLocationIndex + delta;
175
+
176
+ // Validate that the new `index` is not out of bounds.
177
+ if (index < 0 || index > this._terminalLocationIndex) {
178
+ throw new NavigationOutOfBoundsError(index);
179
+ }
180
+
181
+ // Navigate to the location.
182
+ const locationResult = this._navigation.shift({
183
+ operation: NavigationOperations.SHIFT,
184
+ index,
185
+ delta,
186
+ });
187
+
188
+ if (locationResult) {
189
+ this._subscription.notifySubscribers(locationResult);
190
+ }
191
+ }
192
+
193
+ // This function is used by navigation.
194
+ _getCurrentLocationIndex = () => {
195
+ return this._currentLocationIndex;
196
+ };
197
+
198
+ _updateTerminalLocationIndex({ operation }) {
199
+ // A `PUSH` navigation sets a new terminal (rightmost) location.
200
+ if (
201
+ operation === NavigationOperations.PUSH ||
202
+ operation === NavigationOperations.INIT
203
+ ) {
204
+ this._terminalLocationIndex = this._currentLocationIndex;
205
+ }
206
+ }
207
+
208
+ _getNextLocationKey() {
209
+ this._locationKeyIndex++;
210
+ return this._locationKeyIndex.toString(36);
211
+ }
212
+
213
+ _isStarted() {
214
+ return !this._stopped && this._currentLocationIndex !== INITIAL_INDEX;
215
+ }
216
+ }
@@ -0,0 +1,13 @@
1
+ import Session from './Session';
2
+ import WebBrowserEnvironment from '../environment/WebBrowserEnvironment';
3
+ import WebBrowserSessionLifecycle from './lifecycle/WebBrowserSessionLifecycle';
4
+ import WebBrowserNavigation from './navigation/WebBrowserNavigation';
5
+
6
+ export default class WebBrowserSession extends Session {
7
+ constructor() {
8
+ super({ navigation: new WebBrowserNavigation() });
9
+
10
+ this.environment = new WebBrowserEnvironment();
11
+ this.lifecycle = new WebBrowserSessionLifecycle();
12
+ }
13
+ }
@@ -0,0 +1,18 @@
1
+ // `session.key` exists to avoid `location.key` collision after a page refresh.
2
+ // After a page refresh, a different `session.key` is created
3
+ // while the previous navigation history still exists because
4
+ // web browser navigation history survives a page reload.
5
+ // So web browser navigation history after a refresh contains records from different sessions.
6
+ // This means that some of those history records end up having same `location.key`s
7
+ // because `location.key` always starts from `0` for each different session.
8
+ // So `location.key` alone can't be used to identify navigation history entries
9
+ // because it's not unique among them. In order to get a unique key for a navigation history entry,
10
+ // one should combine a unique `session.key` with a `location.key`.
11
+ // That's what `session.key` exists for.
12
+ // Supplementary features such as scroll position restoration
13
+ // use `window.sessionStorage` to store supplementary data for a given navigation history entry.
14
+ // Because `window.sessionStorage` is shared between all navigation history entries from different websites,
15
+ // the keys used for storing that supplementary data have to be unique.
16
+ export default function createSessionKey() {
17
+ return Date.now().toString(36);
18
+ }
@@ -0,0 +1,13 @@
1
+ export default class InMemorySessionLifecycle {
2
+ // Termination blockers of an "in-memory session" are currently ignored.
3
+ // eslint-disable-next-line no-unused-vars
4
+ addTerminationBlocker(blocker) {
5
+ return () => {};
6
+ }
7
+
8
+ // An "in-memory session" execution status is always `running: true`.
9
+ // eslint-disable-next-line no-unused-vars
10
+ addExecutionStatusListener(listener) {
11
+ return () => {};
12
+ }
13
+ }
@@ -0,0 +1,126 @@
1
+ // https://developers.google.com/web/updates/2018/07/page-lifecycle-api
2
+ // https://github.com/GoogleChromeLabs/page-lifecycle
3
+ import PageLifecycle from './page-lifecycle/PageLifecycleInstance';
4
+
5
+ export default class WebBrowserSessionLifecycle {
6
+ constructor() {
7
+ this._running = true;
8
+ }
9
+
10
+ addTerminationBlocker(terminationBlocker) {
11
+ const onBeforeUnload = (event) => {
12
+ if (terminationBlocker()) {
13
+ // Calling `event.preventDefault()` will cause a web browser
14
+ // to show a generic "Ok"/"Cancel" modal with some generic text:
15
+ // "Are you sure to leave the current page?".
16
+ event.preventDefault();
17
+ }
18
+ };
19
+
20
+ window.addEventListener('beforeunload', onBeforeUnload);
21
+ return () => {
22
+ window.removeEventListener('beforeunload', onBeforeUnload);
23
+ };
24
+ }
25
+
26
+ addExecutionStatusListener(listener) {
27
+ const pageLifecycleListener = (stateChange) => {
28
+ // (stateChange: PageLifecycleStateChange)
29
+ const { newState } = stateChange;
30
+
31
+ const running = !['terminated', 'frozen', 'discarded'].includes(
32
+ newState,
33
+ );
34
+
35
+ if (this._running !== running) {
36
+ this._running = running;
37
+ listener({ running });
38
+ }
39
+ };
40
+
41
+ PageLifecycle.addEventListener('statechange', pageLifecycleListener);
42
+
43
+ return () => {
44
+ PageLifecycle.removeEventListener('statechange', pageLifecycleListener);
45
+ };
46
+ }
47
+ }
48
+
49
+ // interface PageLifecycleStateChange {
50
+ // newState: PageLifecycleState;
51
+ // oldState: PageLifecycleState;
52
+ // originalEvent: Event;
53
+ // }
54
+
55
+ // // Page Lifecycle API event types.
56
+ // // https://developer.chrome.com/docs/web-platform/page-lifecycle-api#states
57
+ // // https://wicg.github.io/page-lifecycle/spec.html
58
+ // //
59
+ // type PageLifecycleState =
60
+ // // The page is visible and is focused.
61
+ // | 'active'
62
+ // // The page is visible but is not focused.
63
+ // | 'passive'
64
+ // // The page is not visible (and has not been frozen, discarded, or terminated).
65
+ // | 'hidden'
66
+ // // If a page is hidden, a browser may choose to freeze it to reduce energy consumption.
67
+ // | 'frozen'
68
+ // // The process of terminating (destroying, closing) the page has started.
69
+ // | 'terminated'
70
+ // // The page is discarded by the web browser due to insufficient resources.
71
+ // // The page snapshot could still be visible to the user even though it's no longer running.
72
+ // | 'discarded';
73
+
74
+ // // Page Lifecycle API event types.
75
+ // // https://developer.chrome.com/docs/web-platform/page-lifecycle-api
76
+ // // https://wicg.github.io/page-lifecycle/spec.html
77
+ // //
78
+ // type PageLifecycleEvent =
79
+ // // When the web browser window with an opened page gets focus, a `focus` event is emitted.
80
+ // | 'focus'
81
+ //
82
+ // // When the web browser window with an opened page is no longer focused, a `blur` event is emitted.
83
+ // | 'blur'
84
+ //
85
+ // // `visibilitychange` event fires with `document.visibilityState` being "hidden"
86
+ // // when a user navigates to a new page, switches tabs, closes the tab, minimizes or closes the browser,
87
+ // // or, on mobile, switches from the browser to a different app.
88
+ // //
89
+ // // Transitioning to "hidden" is the last event that's reliably observable by the page,
90
+ // // so developers should treat it as the likely end of the user's session
91
+ // // (for example, for sending analytics data).
92
+ // //
93
+ // // The transition to "hidden" is also a good point at which pages can stop making UI updates
94
+ // // and stop any tasks that the user doesn't want to have running in the background.
95
+ // //
96
+ // | 'visibilitychange'
97
+ //
98
+ // // Sometimes browsers "freeze" hidden pages in order to reduce energy consumption on mobile devices.
99
+ // // In case of freezing an already-hidden page, a `freeze` event will be emitted, if supported by the browser.
100
+ // | 'freeze'
101
+ //
102
+ // // Sometimes browsers "freeze" hidden pages in order to reduce energy consumption on mobile devices.
103
+ // // In case of unfreezing an already-frozen page, a `resume` event will be emitted, if supported by the browser.
104
+ // | 'resume'
105
+ //
106
+ // // `pageshow` event is emitted when a new page gets shown.
107
+ // //
108
+ // // For example, `pageshow` event is emitted when visiting a web page
109
+ // // or after being navigated to a new page by clicking a hyperlink.
110
+ // //
111
+ // // `pageshow` event is also emitted when the user performs "Back" or "Forward" transition.
112
+ // //
113
+ // | 'pageshow'
114
+ //
115
+ // // `pagehide` event is emitted when the current page gets "destroyed".
116
+ // //
117
+ // // For example, `pagehide` event is emitted when the user performs "Back" or "Forward" transition.
118
+ // // In that case, `pagehide` event will be emitted for the current page before the transition.
119
+ // //
120
+ // // In any other cases of "destroying" The current page, `pagehide` event is not guaranteed to be emitted.
121
+ // // For example, it won't be emitted when closing the web browser app via a task manager.
122
+ // //
123
+ // // Hence, `pagehide` event is unreliable and it's adivised to use `visibilitychange` event instead.
124
+ // // Only if `visibilitychange` even is not supported by a web browser should one consider resorting to using `pagehide` event.
125
+ // //
126
+ // | 'pagehide';