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
@@ -1,34 +0,0 @@
1
- import ActionTypes from '../ActionTypes';
2
- import { getBeforeLocationChangeListeners, removeAllBeforeLocationChangeListeners, runBeforeLocationChangeListeners } from '../beforeLocationChangeListeners';
3
-
4
- // Creates a "middleware" that calls upcoming navigation listeners.
5
- export default function createBeforeLocationChangeListenerMiddleware(session) {
6
- return function navigationListenerMiddleware() {
7
- return next => action => {
8
- const {
9
- type,
10
- payload
11
- } = action;
12
- switch (type) {
13
- // Trigger navigation listeners before the `location` has been updated in Redux state.
14
- // It doesn't matter that the new location URL has already been updated in the web browser's
15
- // address bar, or that the web browser's history has already switched to the new locaiton.
16
- // From the application's point of view, all of that doesn't matter and even doesn't exist.
17
- // All that exists from the application's point of view is the `location` object in the Redux state.
18
- // Until the `location` object in the Redux state is updated, the old page is still rendered.
19
- // The appliation is only concerned with the updates of the `location` object in the Redux state
20
- // and completely ignores any updates to the URL in the web browser's address bar.
21
- case ActionTypes.UPDATE:
22
- runBeforeLocationChangeListeners(getBeforeLocationChangeListeners(session), payload);
23
- return next(action);
24
-
25
- // Remove any navigation listeners on `DISPOSE` event.
26
- case ActionTypes.DISPOSE:
27
- removeAllBeforeLocationChangeListeners(session);
28
- return next(action);
29
- default:
30
- return next(action);
31
- }
32
- };
33
- };
34
- }
@@ -1,50 +0,0 @@
1
- import ActionTypes from '../ActionTypes';
2
- function updateLocation(location) {
3
- return {
4
- type: ActionTypes.UPDATE,
5
- payload: location
6
- };
7
- }
8
-
9
- // Creates a "middleware" that performs the actual navigation according to the `session` being used.
10
- // For example, when `BrowserSession` is used, it calls methods of the `window.history` object.
11
- export default function createLocationMiddleware(session, {
12
- shouldIgnoreLocationSubscriptionEvents
13
- }) {
14
- return function locationMiddleware() {
15
- return next => {
16
- // Whenever browser location changes,
17
- // perform the same changes with the internal `location` object.
18
- const unsubscribe = session.navigation.subscribe(location => {
19
- if (!shouldIgnoreLocationSubscriptionEvents()) {
20
- next(updateLocation(location));
21
- }
22
- });
23
- return action => {
24
- const {
25
- type,
26
- payload
27
- } = action;
28
- switch (type) {
29
- case ActionTypes.INIT:
30
- return next(updateLocation(session.navigation.init()));
31
- case ActionTypes.NAVIGATE:
32
- // `session.navigate()` doesn't trigger the `subscribe()` listener.
33
- return next(updateLocation(session.navigation.navigate(payload)));
34
- case ActionTypes.SHIFT:
35
- // `shift()` will trigger the `subscribe()` listener,
36
- // which will call `updateLocation()`.
37
- session.navigation.shift(payload);
38
- // eslint-disable-next-line consistent-return
39
- return;
40
- case ActionTypes.DISPOSE:
41
- unsubscribe();
42
- // eslint-disable-next-line consistent-return
43
- return;
44
- default:
45
- return next(action);
46
- }
47
- };
48
- };
49
- };
50
- }
@@ -1,156 +0,0 @@
1
- import ActionTypes from '../ActionTypes';
2
- import isPromise from '../isPromise';
3
- import { getNavigationBlockers, removeAllNavigationBlockers, runNavigationBlockers } from '../navigationBlockers';
4
-
5
- // Creates a "middleware" that applies navigation blockers.
6
- export default function createNavigationBlockerMiddleware(session, {
7
- ignoreLocationSubscriptionEvents
8
- }) {
9
- function createNavigationBlockersEvaluationStatus() {
10
- /* eslint-disable no-underscore-dangle */
11
- if (session._navigationBlockersEvaluationStatus) {
12
- session._navigationBlockersEvaluationStatus.cancelled = true;
13
- }
14
- session._navigationBlockersEvaluationStatus = {
15
- cancelled: false
16
- };
17
- return session._navigationBlockersEvaluationStatus;
18
- }
19
- return function navigationBlockerMiddleware() {
20
- return next => action => {
21
- const {
22
- type,
23
- payload
24
- } = action;
25
-
26
- // Declaring `result` variable here fixes ESLint error:
27
- // "Unexpected lexical declaration in case block".
28
- let result;
29
- switch (type) {
30
- // Prevent or allow navigation that was initiated by the application
31
- // by dispatching a `.push()` or `.replace()` action.
32
- //
33
- // It doesn't handle `.shift()` navigation actions because it doesn't yet know
34
- // the `location` that it's gonna `shift` to. Instead, it waits for the web browser
35
- // to "shift" to that `location` and then reads it and rewinds the "shift"
36
- // if it should've been blocked. That is handled in the `case ActionTypes.UPDATE` block.
37
- //
38
- // This type of "shifting" and then rewinding the "shift" doesn't really matter to the application at all.
39
- // From the application's point of view, all of that doesn't matter and even doesn't exist.
40
- // All that exists from the application's point of view is the `location` object in the Redux state.
41
- // Until the `location` object in the Redux state is updated, the old page is still rendered.
42
- // The appliation is only concerned with the updates of the `location` object in the Redux state
43
- // and completely ignores any updates to the URL in the web browser's address bar.
44
- //
45
- case ActionTypes.NAVIGATE:
46
- // `resultValue` variable name works around a stupid javascript error:
47
- // "Cannot redeclare block-scoped variable 'result'".
48
- result = runNavigationBlockers(getNavigationBlockers(session), payload);
49
- if (isPromise(result)) {
50
- const status = createNavigationBlockersEvaluationStatus();
51
- // eslint-disable-next-line consistent-return
52
- result.then(resultValue => {
53
- if (!status.cancelled) {
54
- if (!resultValue) {
55
- return next(action);
56
- }
57
- }
58
- });
59
- } else if (!result) {
60
- return next(action);
61
- }
62
- // eslint-disable-next-line consistent-return
63
- return;
64
-
65
- // One can notice that this "middleware" handles both `NAVIGATE` and `UPDATE` Redux actions,
66
- // even though a `NAVIGATE` action normally always causes a follow-up `UPDATE` Redux action.
67
- // There's no contradiction here: if a navigation blocker should block a certain navigation,
68
- // it will do that at the `NAVIGATION` stage and it won't get to the `UPDATE` stage, so it
69
- // won't be called "second time" or something like that.
70
- //
71
- // One could ask then: Why handle `UPDATE` Redux action at all?
72
- // The reason why it handles `UPDATE` Redux actions here is because
73
- // `NAVIGATE` Redux actions are only emitted for programmatic "push" or "replace" navigation
74
- // initiated by the application code, and there're other cases of navigation such as
75
- // programmatic "shift" navigation or when the user manually clicks "Back" or "Forward" button
76
- // in a web browser. Such "other" cases could only be handled by reacting to an `UPDATE` Redux action
77
- // which is only emitted after the URL in the browser's address bar has changed.
78
- //
79
- // But there's no real drawback in reacting to an `UPDATE` Redux action "post factum" because
80
- // from the application's point of view the address bar doesn't matter and even doesn't exist.
81
- // All that exists from the application's point of view is the `location` object in the Redux state.
82
- // Until the `location` object in the Redux state is updated, the old page is still rendered.
83
- // The appliation is only concerned with the updates of the `location` object in the Redux state
84
- // and completely ignores any updates to the URL in the web browser's address bar.
85
- //
86
- // So here, the "middleware" attempts to prevent or allow navigation that has already happened
87
- // in the web browser's address bar but hasn't yet happened in Redux state.
88
- // For example, it could be a user clicking a "Back"/"Forward" button in their web browser.
89
- // If such navigation should've been blocked, it will simply not update the `locaiton` object in Redux state,
90
- // and it will also "rewind" the change of the URL in the web browser's address bar so that it's consistent
91
- // with the `location` in Redux state.
92
- //
93
- case ActionTypes.UPDATE:
94
- // If no navigation blockers to run, don't do anything.
95
- if (getNavigationBlockers(session).length === 0) {
96
- return next(action);
97
- }
98
-
99
- // If it was the initial page load or a redirect,
100
- // it's not really a navigation that could be rolled back.
101
- if (payload.delta === 0) {
102
- return next(action);
103
- }
104
-
105
- // It's not really possible for a location to not have a `delta` property in a web browser session.
106
- // So this case is not something that's supposed to happen in real life.
107
- // Rather, it's a guard against an unsupported or incorrect session implementation or something like that.
108
- // If there's no `delta` property on the location, it means that the previous location can't be rewound to,
109
- // so it can't really "prevent" the navigation that has just happened.
110
- if (payload.delta === null) {
111
- return next(action);
112
- }
113
- result = runNavigationBlockers(getNavigationBlockers(session), payload);
114
- if (isPromise(result)) {
115
- const status = createNavigationBlockersEvaluationStatus();
116
-
117
- // While location blockers are running, rewind to the previous location.
118
- ignoreLocationSubscriptionEvents(() => {
119
- session.navigation.shift(-payload.delta);
120
- });
121
- result.then(promiseResult => {
122
- if (promiseResult) {
123
- // Navigation blocked.
124
- // Already rewound to a previous location.
125
- } else if (!status.cancelled) {
126
- // Navigation not blocked.
127
- // Rewind back to the new location.
128
- ignoreLocationSubscriptionEvents(() => {
129
- session.navigation.shift(payload.delta);
130
- });
131
- // Update the location.
132
- next(action);
133
- }
134
- });
135
- } else if (result) {
136
- // Prevent the navigation: rewind to the previous location.
137
- ignoreLocationSubscriptionEvents(() => {
138
- session.navigation.shift(-payload.delta);
139
- });
140
- } else {
141
- // Update the location.
142
- return next(action);
143
- }
144
- // eslint-disable-next-line consistent-return
145
- return;
146
-
147
- // Remove any navigation blockers on `DISPOSE` event.
148
- case ActionTypes.DISPOSE:
149
- removeAllNavigationBlockers(session);
150
- return next(action);
151
- default:
152
- return next(action);
153
- }
154
- };
155
- };
156
- }
@@ -1,33 +0,0 @@
1
- import ActionTypes from '../ActionTypes';
2
-
3
- // Creates a "middleware" that transforms action payload (location).
4
- export default function createTransformLocationMiddleware({
5
- transformInputLocation,
6
- transformSubscriptionLocation
7
- }) {
8
- return function transformLocationMiddleware() {
9
- return next => action => {
10
- const {
11
- type,
12
- payload
13
- } = action;
14
- switch (type) {
15
- // Transforms `NAVIGATE` action payload (`location`).
16
- case ActionTypes.NAVIGATE:
17
- return next({
18
- type,
19
- payload: transformInputLocation(payload)
20
- });
21
-
22
- // Transforms `UPDATE` action payload (input `location`).
23
- case ActionTypes.UPDATE:
24
- return next({
25
- type,
26
- payload: transformSubscriptionLocation(payload)
27
- });
28
- default:
29
- return next(action);
30
- }
31
- };
32
- };
33
- }
@@ -1,5 +0,0 @@
1
- export default function onlyAllowedOnClientSide() {
2
- if (typeof window === 'undefined') {
3
- throw new Error('This function can only be called on client side');
4
- }
5
- }
@@ -1,229 +0,0 @@
1
- const _excluded = ["delta"];
2
- function _objectWithoutPropertiesLoose(r, e) { if (null == r) return {}; var t = {}; for (var n in r) if ({}.hasOwnProperty.call(r, n)) { if (-1 !== e.indexOf(n)) continue; t[n] = r[n]; } return t; }
3
- /* eslint-disable max-classes-per-file */
4
-
5
- import getLocationUrl from '../getLocationUrl';
6
- import parseQueryFromSearch from '../parseQueryFromSearch';
7
- const INITIAL_KEY_INDEX = -1;
8
- const INITIAL_INDEX = -1;
9
- const INIT_LOCATION_DELTA = 0;
10
-
11
- // A web browser has a notion of a "navigation history".
12
- // A "navigation history" exists within a given web browser's tab.
13
- // The user can click "Back" or "Forward" buttons in the web browser and it will automatically load
14
- // "previous" or "next" page from scratch.
15
- //
16
- // Later, web browsers added a `window.history` object that the application can,
17
- // but isn't required to, interact with. That `window.history` object allows the application
18
- // to programmatically control the URL in the address bar of the web browser, as well as
19
- // the "navigation history" by programmatically adding new entries to it or reading the current entry,
20
- // and it also allows the application to override the default web browser's behavior
21
- // when the user clicks "Back" or "Forward" buttons in the web browser.
22
- //
23
- // Specifically, the `window.history` object has a method called `.pushState()` which programmatically adds
24
- // a new entry in the "navigation history" and updates the URL in the address bar and also
25
- // tells the web browser that starting from the entry before this new entry in the "navigation history",
26
- // the application would prefer to manually handle any "Back"/"Forward" transition when the user clicks
27
- // those "Back" or "Forward" buttons in the web browser, and this behavior should persist for any future
28
- // "navigation history" entries programmatically added by the application via `window.history.pushState()`,
29
- // and will only stop if the user navigates from the page by the means of conventional navigation,
30
- // that is by clicking a standard hyperlink, at which point the current page gets "destroyed".
31
- //
32
- // So for manually "pushed" entries of the "navigation history", the web browser won't load those pages
33
- // from scratch after a user-initiated "Back" or "Forward" transition. In fact, it won't do anything and
34
- // it will just step aside and let the application itself do those transitions. The web browser will only
35
- // update the URL in the address bar and that's it.
36
- //
37
- // This whole thing allows the application to:
38
- //
39
- // * Load the "previous" or "next" page much faster than when using the default "from scratch" approach
40
- // because it doesn't have to destroy the current page, then send a new HTTP request to the server,
41
- // then parse the HTML response and initialize a new page, re-download all those images, etc.
42
- //
43
- // * Optionally render a snapshotted verison of the "previous" page thereby "restoring" the "previous" page
44
- // rather than reloading it from scratch, i.e. the state of the "previous" page could be fully restored.
45
- //
46
- class BrowserNavigation {
47
- constructor() {
48
- // `this._keyPrefix` exists to avoid `this._keyIndex` collision after a page refresh.
49
- // After a page refresh, `this._keyIndex` is reset to `0` while the previous navigation history
50
- // still exists because web browser navigation history survives a page reload.
51
- this._keyPrefix = Date.now().toString(36);
52
- // `this._keyIndex` is incremented every time the current location changes.
53
- this._keyIndex = INITIAL_KEY_INDEX;
54
-
55
- // `this._index` is the index of the top element in the navigation stack.
56
- // I.e. it's the index of the "current" location in the navigation stack.
57
- this._index = INITIAL_INDEX;
58
- }
59
- init() {
60
- return this._createEntryFromCurrentLocation('INIT');
61
- }
62
- _createEntryFromCurrentLocation(action) {
63
- const {
64
- pathname,
65
- search,
66
- hash
67
- } = window.location;
68
- const isSettingInitialLocation = this._index === INITIAL_INDEX;
69
- if (action === 'INIT' && !isSettingInitialLocation) {
70
- throw Error('Browser session has already been initialized');
71
- }
72
- if (isSettingInitialLocation && action !== 'INIT') {
73
- throw Error('Browser session must be initialized before reacting to location changes');
74
- }
75
- const {
76
- key,
77
- index,
78
- delta,
79
- state
80
- } = isSettingInitialLocation ? this._createAdditionalPropertiesForNewLocation({
81
- delta: 1,
82
- state: undefined
83
- }) : this._restoreAdditionalPropertiesForCurrentLocation();
84
- return {
85
- action,
86
- pathname,
87
- search,
88
- query: parseQueryFromSearch(search),
89
- hash,
90
- key,
91
- index,
92
- delta: isSettingInitialLocation ? INIT_LOCATION_DELTA : delta,
93
- state
94
- };
95
- }
96
-
97
- // Subscribes to changes in location,
98
- // excluding ones that happened as a result of calling `.navigate()`.
99
- subscribe(listener) {
100
- const onPopState = () => {
101
- listener(this._createEntryFromCurrentLocation('SHIFT'));
102
- };
103
- window.addEventListener('popstate', onPopState);
104
- return () => {
105
- window.removeEventListener('popstate', onPopState);
106
- };
107
- }
108
- navigate(location) {
109
- const {
110
- action,
111
- state
112
- } = location;
113
- if (action !== 'PUSH' && action !== 'REPLACE') {
114
- throw Error(`Unrecognized browser session action: ${action}`);
115
- }
116
- if (this._index === INITIAL_INDEX) {
117
- throw Error('Browser session must be initialized before navigation');
118
- }
119
- const delta = action === 'PUSH' ? 1 : 0;
120
- const additionalProperties = this._createAdditionalPropertiesForNewLocation({
121
- delta,
122
- state
123
- });
124
- this._storeAdditionalPropertiesForLocation(location, additionalProperties);
125
- return Object.assign({}, location, additionalProperties);
126
- }
127
- shift(delta) {
128
- window.history.go(delta);
129
- }
130
- _createKeyForKeyIndex(keyIndex) {
131
- return `${this._keyPrefix}.${keyIndex.toString(36)}`;
132
- }
133
- _createAdditionalPropertiesForNewLocation({
134
- delta,
135
- state
136
- }) {
137
- this._keyIndex++;
138
- this._index += delta;
139
- return {
140
- key: this._createKeyForKeyIndex(this._keyIndex),
141
- index: this._index,
142
- delta,
143
- state
144
- };
145
- }
146
- _restoreAdditionalPropertiesForCurrentLocation() {
147
- // Initial location doesn't have any `window.history.state` assigned to it
148
- // because it wasn't navigated to via a `window.history.pushState()` method.
149
- // Because of that, the additional properties for the initial location can't be read
150
- // from `window.history.state` and have to be reconstructed manually.
151
- const {
152
- key,
153
- index,
154
- state
155
- } = window.history.state || this._getAdditionalPropertiesForInitialLocation();
156
- const delta = index - this._index;
157
- this._index = index;
158
- return {
159
- key,
160
- index,
161
- delta,
162
- state
163
- };
164
- }
165
- _storeAdditionalPropertiesForLocation(location, additionalProperties) {
166
- const url = getLocationUrl(location);
167
- // `delta` property is not stored in `window.history.state`
168
- // because it is supposed to be recalculated every time when reading from `window.history.state`.
169
- const {
170
- delta
171
- } = additionalProperties,
172
- restProperties = _objectWithoutPropertiesLoose(additionalProperties, _excluded);
173
- if (delta === 1) {
174
- window.history.pushState(restProperties, null, url);
175
- } else if (delta === 0) {
176
- window.history.replaceState(restProperties, null, url);
177
- } else {
178
- throw new Error(`Unexpected \`delta\` when storing additional properties for location: ${delta}`);
179
- }
180
- }
181
-
182
- // Initial location doesn't have any `window.history.state` assigned to it
183
- // because it wasn't navigated to via a `window.history.pushState()` method.
184
- // Because of that, the additional properties for the initial location can't be read
185
- // from `window.history.state` and have to be reconstructed manually.
186
- _getAdditionalPropertiesForInitialLocation() {
187
- return {
188
- key: this._createKeyForKeyIndex(INITIAL_KEY_INDEX + 1),
189
- index: INITIAL_INDEX + 1,
190
- delta: INIT_LOCATION_DELTA,
191
- state: undefined
192
- };
193
- }
194
- }
195
- class BrowserDataStorage {
196
- // Returns either a `string` value or `null` if the key doesn't exist.
197
- get(key) {
198
- // `sessionStorage` persists across page reloads, and so does web browser navigation history.
199
- return window.sessionStorage.getItem(key);
200
- }
201
- remove(key) {
202
- // `sessionStorage` persists across page reloads, and so does web browser navigation history.
203
- window.sessionStorage.removeItem(key);
204
- }
205
- set(key, value) {
206
- // `sessionStorage` persists across page reloads, and so does web browser navigation history.
207
- window.sessionStorage.setItem(key, value);
208
- }
209
- }
210
- export default class BrowserSession {
211
- constructor() {
212
- this.navigation = new BrowserNavigation();
213
- this.dataStorage = new BrowserDataStorage();
214
- }
215
- addBeforeDestroyListener(onBeforeDestroy) {
216
- const onBeforeUnload = event => {
217
- if (onBeforeDestroy()) {
218
- // Calling `event.preventDefault()` will cause a web browser
219
- // to show a generic "Ok"/"Cancel" modal with some generic text:
220
- // "Are you sure to leave the current page?".
221
- event.preventDefault();
222
- }
223
- };
224
- window.addEventListener('beforeunload', onBeforeUnload);
225
- return () => {
226
- window.removeEventListener('beforeunload', onBeforeUnload);
227
- };
228
- }
229
- }