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,12 @@
1
+ "use strict";
2
+
3
+ exports.__esModule = true;
4
+ exports.default = void 0;
5
+ class NavigationOutOfBoundsError extends Error {
6
+ constructor(index) {
7
+ super(`Location index ${index} is out of navigation history bounds`);
8
+ this.index = index;
9
+ }
10
+ }
11
+ exports.default = NavigationOutOfBoundsError;
12
+ module.exports = exports.default;
@@ -0,0 +1,21 @@
1
+ "use strict";
2
+
3
+ exports.__esModule = true;
4
+ exports.default = void 0;
5
+ var _getLocationUrl = _interopRequireDefault(require("../../../getLocationUrl"));
6
+ const _excluded = ["operation"];
7
+ function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
8
+ 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; }
9
+ class ServerSideNavigationError extends Error {
10
+ constructor(location) {
11
+ super(location ? `Navigate to ${(0, _getLocationUrl.default)(location)}` : 'Navigate to previous or next location');
12
+ if (location) {
13
+ // Remove `operation` property from `location`.
14
+ // eslint-disable-next-line no-unused-vars
15
+ const locationBase = _objectWithoutPropertiesLoose(location, _excluded);
16
+ this.location = locationBase;
17
+ }
18
+ }
19
+ }
20
+ exports.default = ServerSideNavigationError;
21
+ module.exports = exports.default;
@@ -0,0 +1,11 @@
1
+ "use strict";
2
+
3
+ exports.__esModule = true;
4
+ exports.default = void 0;
5
+ var _default = exports.default = {
6
+ INIT: 'INIT',
7
+ PUSH: 'PUSH',
8
+ REPLACE: 'REPLACE',
9
+ SHIFT: 'SHIFT'
10
+ };
11
+ module.exports = exports.default;
@@ -0,0 +1,81 @@
1
+ "use strict";
2
+
3
+ exports.__esModule = true;
4
+ exports.default = void 0;
5
+ class Subscription {
6
+ constructor({
7
+ activateSubscription
8
+ } = {}) {
9
+ this.notifySubscribers = argument => {
10
+ // `._latest` is only used in tests.
11
+ this._latest = argument;
12
+ for (const {
13
+ listener
14
+ } of this._listeners) {
15
+ listener(argument);
16
+ }
17
+ };
18
+ this._activateSubscription = activateSubscription;
19
+
20
+ // This property is accessed in tests.
21
+ this._listeners = [];
22
+ }
23
+ subscribe(listener) {
24
+ // If subscriptions are stopped, i.e. no new subscriptions are to be added,
25
+ // then don't add any listeners and return a "do nothing" function.
26
+ if (this._stopped) {
27
+ return () => {};
28
+ }
29
+
30
+ // Creating a `listenerEntry` object ensures that the `.filter()` function
31
+ // during "unsubscribe" step doesn't accidentally remove another listeners
32
+ // having the same `listener` function.
33
+ // I.e. it's not illegal to call `.subscribe(listener)` multiple times
34
+ // with the same argument, and those would be considered different subscriptions.
35
+ const listenerEntry = {
36
+ listener
37
+ };
38
+
39
+ // If it's the first listener, activate subscription.
40
+ if (this._listeners.length === 0) {
41
+ this._deactivateSubscription = this._activateSubscription(this.notifySubscribers);
42
+ }
43
+
44
+ // Add the `listener` to the list.
45
+ this._listeners.push(listenerEntry);
46
+
47
+ // The returned `unsubscribe()` function is "idempotent", i.e. it can be called multiple times.
48
+ return () => {
49
+ // Remove the listener, if not already removed.
50
+ this._removeListener(listenerEntry);
51
+ };
52
+ }
53
+ stop() {
54
+ if (this._stopped) {
55
+ throw new Error('Already stopped');
56
+ }
57
+ this._stopped = true;
58
+
59
+ // Clear any remaining listeners.
60
+ for (const listener of this._listeners.slice()) {
61
+ this._removeListener(listener);
62
+ }
63
+ }
64
+ _removeListener(listenerEntry) {
65
+ // If no listeners are left, no need to do anything.
66
+ if (this._listeners.length === 0) {
67
+ return;
68
+ }
69
+
70
+ // Remove the `listener` from the list.
71
+ this._listeners = this._listeners.filter(_ => _ !== listenerEntry);
72
+
73
+ // If it was the last listener, deactivate subscription.
74
+ if (this._listeners.length === 0) {
75
+ this._deactivateSubscription();
76
+ this._deactivateSubscription = undefined;
77
+ }
78
+ }
79
+ }
80
+ exports.default = Subscription;
81
+ module.exports = exports.default;
@@ -0,0 +1,35 @@
1
+ // TypeScript Version: 3.0
2
+
3
+ import { Session } from '../index.d.js';
4
+
5
+ export {};
6
+
7
+ export type GenericValue =
8
+ | string
9
+ | number
10
+ | boolean
11
+ | Record<string, unknown>
12
+ | null
13
+ | undefined;
14
+
15
+ export class DataStorage<
16
+ Key extends string = string,
17
+ Value extends GenericValue = GenericValue,
18
+ > {
19
+ constructor(session: Session, options: { namespace: string });
20
+
21
+ get(key: Key): Value;
22
+
23
+ set(key: Key, value: Value): void;
24
+ }
25
+
26
+ export class LocationDataStorage<
27
+ Key extends string = string,
28
+ Value extends GenericValue = GenericValue,
29
+ > {
30
+ constructor(session: Session, options: { namespace: string });
31
+
32
+ get(location: Location, key: Key): Value;
33
+
34
+ set(location: Location, key: Key, value: Value): void;
35
+ }
@@ -0,0 +1,66 @@
1
+ import { applyMiddleware, createStore } from 'redux';
2
+ import Actions from './redux/Actions';
3
+ import createMiddlewares from './redux/createMiddlewares';
4
+ import locationReducer from './redux/locationReducer';
5
+ import ScrollPositionRestoration from './scroll-position/ScrollPositionRestoration';
6
+ export default class NavigationStack {
7
+ constructor(session, options) {
8
+ this._session = session;
9
+
10
+ // Create a Redux store.
11
+ this._store = createStore(locationReducer, applyMiddleware(...createMiddlewares(session, options)));
12
+
13
+ // Create `ScrollPositionRestoration`.
14
+ if (options && options.maintainScrollPosition) {
15
+ this._scrollPositionRestoration = new ScrollPositionRestoration(session);
16
+ }
17
+ }
18
+ addScrollableContainer(scrollableContainerKey, scrollableContainer) {
19
+ if (!this._scrollPositionRestoration) {
20
+ throw new Error('`maintainScrollPosition: true` option not passed');
21
+ }
22
+ return this._scrollPositionRestoration.addScrollableContainer(scrollableContainerKey, scrollableContainer);
23
+ }
24
+ subscribe(listener) {
25
+ // Subscribe to any potential Redux state changes.
26
+ return this._store.subscribe(() => {
27
+ // Initially, calls the listener when setting the initial location.
28
+ // After that, calls it on any location change.
29
+ const location = this.current();
30
+ if (!this._latestLocation || location !== this._latestLocation) {
31
+ this._latestLocation = location;
32
+ listener(location);
33
+ }
34
+ });
35
+ }
36
+ init(initialLocation) {
37
+ if (this._latestLocation) {
38
+ throw new Error('Already initialized');
39
+ }
40
+ this._store.dispatch(Actions.init(initialLocation));
41
+ this._latestLocation = this.current();
42
+ }
43
+ current() {
44
+ return this._store.getState();
45
+ }
46
+ push(location) {
47
+ this._store.dispatch(Actions.push(location));
48
+ }
49
+ replace(location) {
50
+ this._store.dispatch(Actions.replace(location));
51
+ }
52
+ shift(delta) {
53
+ this._store.dispatch(Actions.shift(delta));
54
+ }
55
+ stop() {
56
+ if (this._scrollPositionRestoration) {
57
+ this._scrollPositionRestoration.stop();
58
+ }
59
+ this._store.dispatch(Actions.stop());
60
+ }
61
+ locationRendered() {
62
+ if (this._scrollPositionRestoration) {
63
+ this._scrollPositionRestoration.locationRendered(this.current());
64
+ }
65
+ }
66
+ }
@@ -0,0 +1,65 @@
1
+ export default class DataStorage {
2
+ constructor(session, {
3
+ namespace
4
+ }) {
5
+ if (!session.key) {
6
+ throw new Error('`DataStorage` requires a `session.key`');
7
+ }
8
+ this._sessionKey = session.key;
9
+ this._dataStorage = session.environment.dataStorage;
10
+ this._namespace = namespace;
11
+ }
12
+ get(key) {
13
+ const storageKey = this._getStorageKey(key);
14
+ try {
15
+ const value = this._dataStorage.get(storageKey);
16
+ // === null is probably sufficient.
17
+ if (value === null) {
18
+ return undefined;
19
+ }
20
+
21
+ // We want to catch JSON parse errors in case someone separately threw
22
+ // junk into sessionStorage under our namespace.
23
+ return JSON.parse(value);
24
+ } catch (error) {
25
+ // eslint-disable-next-line no-console
26
+ console.error('[navigation-stack] Could not read data from storage');
27
+
28
+ // Pretend that the entry doesn't exist.
29
+ return undefined;
30
+ }
31
+ }
32
+ set(key, value) {
33
+ const storageKey = this._getStorageKey(key);
34
+ if (value === undefined) {
35
+ try {
36
+ this._dataStorage.remove(storageKey);
37
+ } catch (error) {
38
+ // No need to handle errors here.
39
+ // eslint-disable-next-line no-console
40
+ console.error('[navigation-stack] Could not delete data from storage');
41
+ }
42
+ return;
43
+ }
44
+
45
+ // Unlike with read, we want to fail on invalid values here, since the
46
+ // value here is provided by the caller of this method.
47
+ const valueString = JSON.stringify(value);
48
+ try {
49
+ this._dataStorage.set(storageKey, valueString);
50
+ } catch (error) {
51
+ // No need to handle errors here either. If it didn't work, it didn't
52
+ // work. We make no guarantees about actually saving the value.
53
+ // eslint-disable-next-line no-console
54
+ console.error('[navigation-stack] Could not save data in storage');
55
+ }
56
+ }
57
+
58
+ // It could also implement a method that would clean up any data written so far,
59
+ // but so far it doesn't seem to be required by any real-world use case.
60
+ // cleanUp() {}
61
+
62
+ _getStorageKey(key) {
63
+ return `${this._sessionKey}|${this.namespace}|${key}`;
64
+ }
65
+ }
@@ -0,0 +1,22 @@
1
+ import DataStorage from './DataStorage';
2
+ import getLocationUrl from '../getLocationUrl';
3
+ export default class LocationDataStorage {
4
+ constructor(session, {
5
+ namespace
6
+ }) {
7
+ this._storage = new DataStorage(session, {
8
+ namespace
9
+ });
10
+ this._getFallbackLocationKey = getLocationUrl;
11
+ }
12
+ get(location, key) {
13
+ return this._storage.get(this._getKey(location, key));
14
+ }
15
+ set(location, key, value) {
16
+ this._storage.set(this._getKey(location, key), value);
17
+ }
18
+ _getKey(location, key) {
19
+ const locationKey = location.key || this._getFallbackLocationKey(location);
20
+ return `${locationKey}|${key}`;
21
+ }
22
+ }
@@ -0,0 +1,2 @@
1
+ export { default as DataStorage } from './DataStorage';
2
+ export { default as LocationDataStorage } from './LocationDataStorage';
@@ -0,0 +1,8 @@
1
+ import InMemoryDataStorage from './data-storage/InMemoryDataStorage';
2
+ import InMemoryScrollPosition from './scroll-position/InMemoryScrollPosition';
3
+ export default class InMemoryEnvironment {
4
+ constructor() {
5
+ this.dataStorage = new InMemoryDataStorage();
6
+ this.scrollPosition = new InMemoryScrollPosition();
7
+ }
8
+ }
@@ -0,0 +1,8 @@
1
+ import WebBrowserDataStorage from './data-storage/WebBrowserDataStorage';
2
+ import WebBrowserScrollPosition from './scroll-position/WebBrowserScrollPosition';
3
+ export default class WebBrowserEnvironment {
4
+ constructor() {
5
+ this.dataStorage = new WebBrowserDataStorage();
6
+ this.scrollPosition = new WebBrowserScrollPosition();
7
+ }
8
+ }
@@ -0,0 +1,21 @@
1
+ export default class InMemoryDataStorage {
2
+ constructor() {
3
+ this._state = {};
4
+ }
5
+
6
+ // Returns either a `string` value or `null` if the key doesn't exist.
7
+ get(key) {
8
+ if (key in this._state) {
9
+ return this._state[key];
10
+ }
11
+ return null;
12
+ }
13
+ remove(key) {
14
+ if (key in this._state) {
15
+ delete this._state[key];
16
+ }
17
+ }
18
+ set(key, value) {
19
+ this._state[key] = value;
20
+ }
21
+ }
@@ -0,0 +1,15 @@
1
+ export default class WebBrowserDataStorage {
2
+ // Returns either a `string` value or `null` if the key doesn't exist.
3
+ get(key) {
4
+ // `sessionStorage` persists across page reloads, and so does web browser navigation history.
5
+ return window.sessionStorage.getItem(key);
6
+ }
7
+ remove(key) {
8
+ // `sessionStorage` persists across page reloads, and so does web browser navigation history.
9
+ window.sessionStorage.removeItem(key);
10
+ }
11
+ set(key, value) {
12
+ // `sessionStorage` persists across page reloads, and so does web browser navigation history.
13
+ window.sessionStorage.setItem(key, value);
14
+ }
15
+ }
@@ -0,0 +1,38 @@
1
+ export default class InMemoryScrollPosition {
2
+ constructor() {
3
+ this.init();
4
+ }
5
+ getPageScrollPosition() {
6
+ return this._pageScrollPosition || [0, 0];
7
+ }
8
+ setPageScrollPosition(scrollPosition) {
9
+ this._pageScrollPosition = scrollPosition;
10
+ }
11
+
12
+ // eslint-disable-next-line no-unused-vars
13
+ setPageScrollPositionAtAnchor(anchor) {
14
+ this.setPageScrollPosition([0, 0]);
15
+ }
16
+ getScrollableContainerScrollPosition(key) {
17
+ return this._scrollableContainerScrollPositions[key] || [0, 0];
18
+ }
19
+ setScrollableContainerScrollPosition(key, scrollPosition) {
20
+ this._scrollableContainerScrollPositions[key] = scrollPosition;
21
+ }
22
+
23
+ // eslint-disable-next-line no-unused-vars
24
+ addPageScrollListener(listener) {
25
+ return () => {};
26
+ }
27
+
28
+ // eslint-disable-next-line no-unused-vars
29
+ addScrollableContainerScrollListener(scrollableContainerElement, listener) {
30
+ return () => {};
31
+ }
32
+ enableAutomaticScrollRestoration() {}
33
+ disableAutomaticScrollRestoration() {}
34
+ init() {
35
+ this._pageScrollPosition = undefined;
36
+ this._scrollableContainerScrollPositions = {};
37
+ }
38
+ }
@@ -0,0 +1,54 @@
1
+ export default class WebBrowserScrollPosition {
2
+ getPageScrollPosition() {
3
+ return [window.pageXOffset, window.pageYOffset];
4
+ }
5
+ setPageScrollPosition(scrollPosition) {
6
+ const [scrollX, scrollY] = scrollPosition;
7
+ window.scrollTo(scrollX, scrollY);
8
+ }
9
+ setPageScrollPositionAtAnchor(anchor) {
10
+ const anchorElement = document.getElementById(anchor) || document.getElementsByName(anchor)[0];
11
+ if (anchorElement) {
12
+ // By default it scrolls the element into view
13
+ // so that it's visible at the top of the window.
14
+ anchorElement.scrollIntoView();
15
+ } else {
16
+ this.setPageScrollPosition([0, 0]);
17
+ }
18
+ }
19
+ getScrollableContainerScrollPosition(scrollableContainerElement) {
20
+ return [scrollableContainerElement.scrollLeft, scrollableContainerElement.scrollTop];
21
+ }
22
+ setScrollableContainerScrollPosition(scrollableContainerElement, scrollPosition) {
23
+ const [scrollX, scrollY] = scrollPosition;
24
+ scrollableContainerElement.scrollLeft = scrollX;
25
+ scrollableContainerElement.scrollTop = scrollY;
26
+ }
27
+ addPageScrollListener(listener) {
28
+ // eslint-disable-next-line no-unused-vars
29
+ const scrollListener = event => {
30
+ listener();
31
+ };
32
+ window.addEventListener('scroll', scrollListener);
33
+ return () => {
34
+ window.removeEventListener('scroll', scrollListener);
35
+ };
36
+ }
37
+ addScrollableContainerScrollListener(scrollableContainerElement, listener) {
38
+ // eslint-disable-next-line no-unused-vars
39
+ const scrollListener = event => {
40
+ listener();
41
+ };
42
+ scrollableContainerElement.addEventListener('scroll', scrollListener);
43
+ return () => {
44
+ scrollableContainerElement.removeEventListener('scroll', scrollListener);
45
+ };
46
+ }
47
+ enableAutomaticScrollRestoration() {
48
+ window.history.scrollRestoration = 'auto';
49
+ }
50
+ disableAutomaticScrollRestoration() {
51
+ window.history.scrollRestoration = 'manual';
52
+ }
53
+ init() {}
54
+ }
@@ -0,0 +1,9 @@
1
+ const _excluded = ["operation", "index", "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
+ // Converts `LocationInternal` object to a publicly-visible `Location` object.
4
+ // It hides non-essential properties of location such as `operation`, `index`, `delta`.
5
+ export default function getLocationFromInternalLocation(internalLocation) {
6
+ // eslint-disable-next-line no-unused-vars
7
+ const location = _objectWithoutPropertiesLoose(internalLocation, _excluded);
8
+ return location;
9
+ }
package/lib/esm/index.js CHANGED
@@ -1,12 +1,14 @@
1
- export { default as Actions } from './Actions';
2
- export { default as ActionTypes } from './ActionTypes';
3
1
  export { addBasePath, removeBasePath } from './basePath';
4
2
  export { default as addNavigationBlocker } from './addNavigationBlocker';
5
3
  export { default as getLocationUrl } from './getLocationUrl';
6
4
  export { default as parseLocationUrl } from './parseLocationUrl';
7
- export { default as createMiddlewares } from './createMiddlewares';
8
- export { default as locationReducer } from './locationReducer';
9
- export { default as LocationDataStorage } from './LocationDataStorage';
10
- export { default as BrowserSession } from './session/BrowserSession';
11
- export { default as MemorySession } from './session/MemorySession';
12
- export { default as ServerSession } from './session/ServerSession';
5
+ export { default as parseInputLocation } from './parseInputLocation';
6
+ export { default as NavigationStack } from './NavigationStack';
7
+ export { default as DataStorage } from './data-storage/DataStorage';
8
+ export { default as LocationDataStorage } from './data-storage/LocationDataStorage';
9
+ export { default as Session } from './session/Session';
10
+ export { default as InMemorySession } from './session/InMemorySession';
11
+ export { default as WebBrowserSession } from './session/WebBrowserSession';
12
+ export { default as ServerSideRenderSession } from './session/ServerSideRenderSession';
13
+ export { default as ServerSideNavigationError } from './session/navigation/error/ServerSideNavigationError';
14
+ export { default as NavigationOutOfBoundsError } from './session/navigation/error/NavigationOutOfBoundsError';
@@ -16,12 +16,12 @@ function removeNavigationBlockerFromTheList(blocker, session) {
16
16
  }
17
17
  }
18
18
  export function removeAllNavigationBlockers(session) {
19
- if (getNavigationBlockers(session).some(blocker => blocker.beforeDestroy)) {
20
- if (!session._removeBeforeDestroyListener) {
21
- throw new Error('`_removeBeforeDestroyListener` property not found in the `session`');
19
+ if (getNavigationBlockers(session).some(blocker => blocker.beforeTermination)) {
20
+ if (!session._removeTerminationBlocker) {
21
+ throw new Error('`_removeTerminationBlocker` property not found in the `session`');
22
22
  }
23
- session._removeBeforeDestroyListener();
24
- session._removeBeforeDestroyListener = undefined;
23
+ session._removeTerminationBlocker();
24
+ session._removeTerminationBlocker = undefined;
25
25
  }
26
26
  session._navigationBlockersList = [];
27
27
  }
@@ -84,33 +84,33 @@ export function runNavigationBlockers(navigationBlockers, toLocation) {
84
84
  }
85
85
 
86
86
  /* istanbul ignore next: not testable with Karma */
87
- function onBeforeDestroy(session) {
87
+ function terminationBlocker(session) {
88
88
  const result = runNavigationBlockers(getNavigationBlockers(session), null);
89
89
 
90
- // If no blocker returned anything, don't prevent the "unload" event.
90
+ // If no blocker returned anything, so don't prevent the navigation.
91
91
  if (!result) {
92
92
  return undefined;
93
93
  }
94
94
 
95
95
  // Web browsers don't allow displaying a custom modal in "beforeunload" phase.
96
96
  // They only allow displaying a standard one, with the default text.
97
- // Hence, "asynchronous" blockers should be ignored.
97
+ // Hence, "asynchronous" blockers should be ignored because web browsers won't wait for those to finish anyway.
98
98
  // https://developer.mozilla.org/en-US/docs/Web/API/Window/beforeunload_event
99
99
  if (isPromise(result)) {
100
100
  return undefined;
101
101
  }
102
102
 
103
- // Prevent the "unload" event.
103
+ // Block the navigation.
104
104
  return true;
105
105
  }
106
106
  export function addNavigationBlocker(session, blocker) {
107
- // All navigation blockers also run on `beforeDestroy` event.
107
+ // All navigation blockers also run on `beforeTermination` event.
108
108
  // If required, this could be a parameter of this function.
109
109
  // The rationale could be that adding a `beforeunload` listener
110
110
  // disables web page caching in some browsers like Firefox.
111
- const beforeDestroy = true;
111
+ const beforeTermination = true;
112
112
 
113
- // If it's the first "beforeDestroy" blocker, add the global `onBeforeDestroy` listener.
113
+ // If it's the first "beforeTermination" blocker, add a `terminationBlocker`.
114
114
  //
115
115
  // Sidenote: Add the "beforeunload" event listener only as needed, as its presence
116
116
  // prevents the page from being added to the page navigation cache:
@@ -120,27 +120,29 @@ export function addNavigationBlocker(session, blocker) {
120
120
  // and this is bad for performance."
121
121
  //
122
122
  // https://developer.mozilla.org/en-US/docs/Web/API/Window/beforeunload_event
123
- if (beforeDestroy && !getNavigationBlockers(session).some(navigationBlocker => navigationBlocker.beforeDestroy)) {
124
- if (session._removeBeforeDestroyListener) {
125
- throw new Error('Unexpected `_removeBeforeDestroyListener` property found in the `session`');
123
+ if (beforeTermination && !getNavigationBlockers(session).some(navigationBlocker => navigationBlocker.beforeTermination)) {
124
+ if (session._removeTerminationBlocker) {
125
+ throw new Error('Unexpected `_removeTerminationBlocker` property found in the `session`');
126
126
  }
127
- session._removeBeforeDestroyListener = session.addBeforeDestroyListener(() => onBeforeDestroy(session));
127
+ session._removeTerminationBlocker = session.lifecycle.addTerminationBlocker(() => {
128
+ return terminationBlocker(session);
129
+ });
128
130
  }
129
131
  const newNavigationBlocker = {
130
132
  blocker,
131
- beforeDestroy
133
+ beforeTermination
132
134
  };
133
135
  addNavigationBlockerToTheList(newNavigationBlocker, session);
134
136
  return () => {
135
137
  removeNavigationBlockerFromTheList(newNavigationBlocker, session);
136
138
 
137
- // If it was the last "beforeDestroy" blocker, remove the global `onBeforeDestroy` listener.
138
- if (beforeDestroy && !getNavigationBlockers(session).some(navigationBlocker => navigationBlocker.beforeDestroy)) {
139
- if (!session._removeBeforeDestroyListener) {
140
- throw new Error('`_removeBeforeDestroyListener` property not found in the `session`');
139
+ // If it was the last "beforeTermination" blocker, remove navigation blocker.
140
+ if (beforeTermination && !getNavigationBlockers(session).some(navigationBlocker => navigationBlocker.beforeTermination)) {
141
+ if (!session._removeTerminationBlocker) {
142
+ throw new Error('`_removeTerminationBlocker` property not found in the `session`');
141
143
  }
142
- session._removeBeforeDestroyListener();
143
- session._removeBeforeDestroyListener = undefined;
144
+ session._removeTerminationBlocker();
145
+ session._removeTerminationBlocker = undefined;
144
146
  }
145
147
  };
146
148
  }
@@ -2,10 +2,10 @@ import createSearchFromQuery from './createSearchFromQuery';
2
2
  import parseLocationUrl from './parseLocationUrl';
3
3
  import parseQueryFromSearch from './parseQueryFromSearch';
4
4
 
5
- // * If `location` is a string, it parses it into a `NormalizedInputLocation`.
5
+ // * If `location` is a string, it parses it into a `LocationBase`.
6
6
  // * If `location` is an object, it ensures that `search` and `hash` properties aren't `undefined`,
7
- // i.e. it "ensures" that the `location` object can be used as a `NormalizedInputLocation`.
8
- export default function normalizeInputLocation(location) {
7
+ // i.e. it "ensures" that the `location` object can be used as a `LocationBase`.
8
+ export default function parseInputLocation(location) {
9
9
  if (typeof location === 'string') {
10
10
  return parseLocationUrl(location);
11
11
  }
@@ -13,7 +13,13 @@ export default function normalizeInputLocation(location) {
13
13
  // Convert `query` property values to strings.
14
14
  if (location.query) {
15
15
  for (const key of Object.keys(location.query)) {
16
- location.query[key] = String(location.query[key]);
16
+ if (typeof location.query[key] !== 'string') {
17
+ location = Object.assign({}, location, {
18
+ query: Object.assign({}, location.query, {
19
+ [key]: String(location.query[key])
20
+ })
21
+ });
22
+ }
17
23
  }
18
24
  }
19
25
 
@@ -32,11 +38,21 @@ export default function normalizeInputLocation(location) {
32
38
  });
33
39
  }
34
40
 
35
- // Set default values on `search` and `hash`
36
- // if those properties are not present.
37
- return Object.assign({}, location, {
41
+ // Set default values for properties that're not present.
42
+ // Ignore unknown properties.
43
+ const {
44
+ state
45
+ } = location;
46
+ location = {
47
+ pathname: location.pathname,
38
48
  query: location.query || {},
39
49
  search: location.search || '',
40
50
  hash: location.hash || ''
41
- });
51
+ };
52
+ if (state) {
53
+ location.state = state;
54
+ }
55
+
56
+ // Return `location`.
57
+ return location;
42
58
  }