navigation-stack 0.3.1 → 0.4.0

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