navigation-stack 0.5.2 → 0.6.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 (210) hide show
  1. package/CHANGELOG.md +16 -0
  2. package/README.md +144 -282
  3. package/karma.conf.cjs +1 -1
  4. package/lib/cjs/NavigationStack.js +138 -49
  5. package/lib/cjs/data-storage/DataStorage.js +7 -6
  6. package/lib/cjs/environment/InMemoryEnvironment.js +6 -0
  7. package/lib/cjs/{session/ServerSideRenderSession.js → environment/ServerSideRenderEnvironment.js} +5 -6
  8. package/lib/cjs/environment/WebBrowserEnvironment.js +6 -0
  9. package/lib/cjs/environment/log/InMemoryLog.js +23 -0
  10. package/lib/cjs/environment/log/WebBrowserLog.js +22 -0
  11. package/lib/cjs/{session → environment}/navigation/InMemoryNavigation.js +16 -5
  12. package/lib/cjs/{session → environment}/navigation/ServerSideNavigation.js +16 -7
  13. package/lib/cjs/{session → environment}/navigation/WebBrowserNavigation.js +48 -8
  14. package/lib/cjs/{session/navigation/error/ServerSideNavigationError.js → environment/navigation/error/ServerSideRedirectError.js} +2 -2
  15. package/lib/cjs/environment/scroll-position/WebBrowserScrollPosition.js +15 -0
  16. package/lib/cjs/getLocationBaseFromLocation.js +14 -0
  17. package/lib/cjs/getLocationUrl.js +3 -5
  18. package/lib/cjs/index.js +10 -16
  19. package/lib/cjs/navigationBlockers.js +34 -32
  20. package/lib/cjs/navigationBlockersEvaluation.js +150 -0
  21. package/lib/cjs/parseInputLocation.js +10 -3
  22. package/lib/cjs/parseQueryFromSearch.js +3 -6
  23. package/lib/cjs/parseQueryString.js +77 -0
  24. package/lib/cjs/scroll-position/ScrollPositionAutoSaver.js +7 -6
  25. package/lib/cjs/scroll-position/ScrollPositionRestoration.js +31 -27
  26. package/lib/cjs/scroll-position/ScrollPositionSaver.js +6 -4
  27. package/lib/cjs/session/Session.js +61 -26
  28. package/lib/cjs/session/subscription/Subscription.js +36 -18
  29. package/lib/cjs/stringifyQuery.js +66 -0
  30. package/lib/cjs/stringifyQueryAsSearch.js +14 -0
  31. package/lib/esm/NavigationStack.js +138 -49
  32. package/lib/esm/data-storage/DataStorage.js +7 -6
  33. package/lib/esm/environment/InMemoryEnvironment.js +6 -0
  34. package/lib/esm/environment/ServerSideRenderEnvironment.js +10 -0
  35. package/lib/esm/environment/WebBrowserEnvironment.js +6 -0
  36. package/lib/esm/environment/log/InMemoryLog.js +17 -0
  37. package/lib/esm/environment/log/WebBrowserLog.js +16 -0
  38. package/lib/esm/{session → environment}/navigation/InMemoryNavigation.js +16 -5
  39. package/lib/esm/{session → environment}/navigation/ServerSideNavigation.js +16 -7
  40. package/lib/esm/{session → environment}/navigation/WebBrowserNavigation.js +48 -8
  41. package/lib/esm/{session/navigation/error/ServerSideNavigationError.js → environment/navigation/error/ServerSideRedirectError.js} +1 -1
  42. package/lib/esm/environment/scroll-position/WebBrowserScrollPosition.js +15 -0
  43. package/lib/esm/getLocationBaseFromLocation.js +9 -0
  44. package/lib/esm/getLocationUrl.js +2 -5
  45. package/lib/esm/index.js +5 -8
  46. package/lib/esm/navigationBlockers.js +34 -32
  47. package/lib/esm/navigationBlockersEvaluation.js +145 -0
  48. package/lib/esm/parseInputLocation.js +9 -3
  49. package/lib/esm/parseQueryFromSearch.js +2 -6
  50. package/lib/esm/parseQueryString.js +72 -0
  51. package/lib/esm/scroll-position/ScrollPositionAutoSaver.js +7 -6
  52. package/lib/esm/scroll-position/ScrollPositionRestoration.js +31 -27
  53. package/lib/esm/scroll-position/ScrollPositionSaver.js +6 -4
  54. package/lib/esm/session/Session.js +61 -26
  55. package/lib/esm/session/subscription/Subscription.js +36 -18
  56. package/lib/esm/stringifyQuery.js +61 -0
  57. package/lib/esm/stringifyQueryAsSearch.js +8 -0
  58. package/lib/index.d.ts +180 -34
  59. package/package.json +4 -7
  60. package/src/NavigationStack.js +166 -56
  61. package/src/data-storage/DataStorage.js +9 -6
  62. package/src/environment/InMemoryEnvironment.js +6 -0
  63. package/src/environment/ServerSideRenderEnvironment.js +10 -0
  64. package/src/environment/WebBrowserEnvironment.js +6 -0
  65. package/src/environment/log/InMemoryLog.js +20 -0
  66. package/src/environment/log/WebBrowserLog.js +18 -0
  67. package/src/{session → environment}/navigation/InMemoryNavigation.js +16 -5
  68. package/src/{session → environment}/navigation/ServerSideNavigation.js +16 -7
  69. package/src/{session → environment}/navigation/WebBrowserNavigation.js +48 -8
  70. package/src/{session/navigation/error/ServerSideNavigationError.js → environment/navigation/error/ServerSideRedirectError.js} +1 -1
  71. package/src/environment/scroll-position/WebBrowserScrollPosition.js +15 -0
  72. package/src/getLocationBaseFromLocation.js +7 -0
  73. package/src/getLocationUrl.js +2 -5
  74. package/src/index.js +10 -13
  75. package/src/navigationBlockers.js +55 -34
  76. package/src/navigationBlockersEvaluation.js +161 -0
  77. package/src/parseInputLocation.js +10 -3
  78. package/src/parseQueryFromSearch.js +2 -6
  79. package/src/parseQueryString.js +81 -0
  80. package/src/scroll-position/ScrollPositionAutoSaver.js +10 -6
  81. package/src/scroll-position/ScrollPositionRestoration.js +36 -30
  82. package/src/scroll-position/ScrollPositionSaver.js +6 -4
  83. package/src/scroll-position/index.js +1 -1
  84. package/src/session/Session.js +68 -24
  85. package/src/session/subscription/Subscription.js +36 -11
  86. package/src/stringifyQuery.js +71 -0
  87. package/src/stringifyQueryAsSearch.js +9 -0
  88. package/test/NavigationStack.addBasePath.test.js +50 -0
  89. package/test/{redux/middleware/createNonProgrammaticNavigationBlockerMiddleware.test.js → NavigationStack.blockNonProgrammaticNavigationIfRequired.test.js} +51 -63
  90. package/test/{redux/middleware/createProgrammaticNavigationBlockerMiddleware.test.js → NavigationStack.blockProgrammaticNavigationIfRequired.test.js} +98 -78
  91. package/test/NavigationStack.general.test.js +68 -0
  92. package/test/NavigationStack.parseInputLocation.test.js +52 -0
  93. package/test/NavigationStack.removeBasePath.test.js +69 -0
  94. package/test/NavigationStack.test.js +97 -29
  95. package/test/data-storage/LocationDataStorage.test.js +3 -2
  96. package/test/index.js +7 -31
  97. package/test/index.test.js +4 -5
  98. package/test/parseQueryFromSearch.test.js +19 -0
  99. package/test/parseQueryString.test.js +18 -0
  100. package/test/scroll-position/ScrollPositionRestoration.test.js +34 -13
  101. package/test/scroll-position/createApp.js +8 -8
  102. package/test/scroll-position/withScrollableContainerAtIndexPageWithDisabledAutomaticScrollPositionRestoration.js +4 -4
  103. package/test/session/{InMemorySession.test.js → Session.InMemoryEnvironment.test.js} +10 -9
  104. package/test/session/{ServerSession.test.js → Session.ServerSideRenderEnvironment.test.js} +5 -4
  105. package/test/session/{WebBrowserSession.test.js → Session.WebBrowserEnvironment.test.js} +63 -13
  106. package/test/shouldWarn.js +44 -0
  107. package/test/stringifyQuery.test.js +65 -0
  108. package/types/index.d.ts +180 -34
  109. package/types/tsconfig.json +0 -1
  110. package/data-storage/package.json +0 -7
  111. package/lib/cjs/createSearchFromQuery.js +0 -13
  112. package/lib/cjs/debug.js +0 -12
  113. package/lib/cjs/redux/ActionTypes.js +0 -14
  114. package/lib/cjs/redux/ActionTypesInternal.js +0 -8
  115. package/lib/cjs/redux/Actions.js +0 -28
  116. package/lib/cjs/redux/createMiddlewares.js +0 -60
  117. package/lib/cjs/redux/index.js +0 -13
  118. package/lib/cjs/redux/internalLocationReducer.js +0 -14
  119. package/lib/cjs/redux/locationReducer.js +0 -13
  120. package/lib/cjs/redux/middleware/createAddInputLocationBasePathMiddleware.js +0 -32
  121. package/lib/cjs/redux/middleware/createNonProgrammaticNavigationBlockerMiddleware.js +0 -113
  122. package/lib/cjs/redux/middleware/createProgrammaticNavigationBlockerMiddleware.js +0 -94
  123. package/lib/cjs/redux/middleware/createRemoveOutputLocationBasePathMiddleware.js +0 -30
  124. package/lib/cjs/redux/middleware/createUpdateInternalLocationMiddleware.js +0 -73
  125. package/lib/cjs/redux/middleware/navigationOperationMiddleware.js +0 -40
  126. package/lib/cjs/redux/middleware/parseInputLocationMiddleware.js +0 -29
  127. package/lib/cjs/redux/middleware/updateLocationMiddleware.js +0 -34
  128. package/lib/cjs/session/InMemorySession.js +0 -22
  129. package/lib/cjs/session/WebBrowserSession.js +0 -20
  130. package/lib/data-storage/index.d.ts +0 -35
  131. package/lib/esm/createSearchFromQuery.js +0 -8
  132. package/lib/esm/debug.js +0 -7
  133. package/lib/esm/redux/ActionTypes.js +0 -9
  134. package/lib/esm/redux/ActionTypesInternal.js +0 -3
  135. package/lib/esm/redux/Actions.js +0 -22
  136. package/lib/esm/redux/createMiddlewares.js +0 -54
  137. package/lib/esm/redux/index.js +0 -4
  138. package/lib/esm/redux/internalLocationReducer.js +0 -8
  139. package/lib/esm/redux/locationReducer.js +0 -7
  140. package/lib/esm/redux/middleware/createAddInputLocationBasePathMiddleware.js +0 -27
  141. package/lib/esm/redux/middleware/createNonProgrammaticNavigationBlockerMiddleware.js +0 -108
  142. package/lib/esm/redux/middleware/createProgrammaticNavigationBlockerMiddleware.js +0 -88
  143. package/lib/esm/redux/middleware/createRemoveOutputLocationBasePathMiddleware.js +0 -25
  144. package/lib/esm/redux/middleware/createUpdateInternalLocationMiddleware.js +0 -68
  145. package/lib/esm/redux/middleware/navigationOperationMiddleware.js +0 -35
  146. package/lib/esm/redux/middleware/parseInputLocationMiddleware.js +0 -24
  147. package/lib/esm/redux/middleware/updateLocationMiddleware.js +0 -28
  148. package/lib/esm/session/InMemorySession.js +0 -15
  149. package/lib/esm/session/ServerSideRenderSession.js +0 -11
  150. package/lib/esm/session/WebBrowserSession.js +0 -13
  151. package/lib/redux/index.d.ts +0 -90
  152. package/lib/scroll-position/index.d.ts +0 -107
  153. package/redux/package.json +0 -7
  154. package/scroll-position/package.json +0 -7
  155. package/src/createSearchFromQuery.js +0 -9
  156. package/src/debug.js +0 -8
  157. package/src/redux/ActionTypes.js +0 -9
  158. package/src/redux/ActionTypesInternal.js +0 -3
  159. package/src/redux/Actions.js +0 -27
  160. package/src/redux/createMiddlewares.js +0 -65
  161. package/src/redux/index.js +0 -4
  162. package/src/redux/internalLocationReducer.js +0 -9
  163. package/src/redux/locationReducer.js +0 -8
  164. package/src/redux/middleware/createAddInputLocationBasePathMiddleware.js +0 -27
  165. package/src/redux/middleware/createNonProgrammaticNavigationBlockerMiddleware.js +0 -119
  166. package/src/redux/middleware/createProgrammaticNavigationBlockerMiddleware.js +0 -94
  167. package/src/redux/middleware/createRemoveOutputLocationBasePathMiddleware.js +0 -26
  168. package/src/redux/middleware/createUpdateInternalLocationMiddleware.js +0 -72
  169. package/src/redux/middleware/navigationOperationMiddleware.js +0 -34
  170. package/src/redux/middleware/parseInputLocationMiddleware.js +0 -23
  171. package/src/redux/middleware/updateLocationMiddleware.js +0 -28
  172. package/src/session/InMemorySession.js +0 -13
  173. package/src/session/ServerSideRenderSession.js +0 -9
  174. package/src/session/WebBrowserSession.js +0 -13
  175. package/test/middlewareTestUtil.js +0 -31
  176. package/test/redux/Action.test.js +0 -73
  177. package/test/redux/ActionTypes.test.js +0 -13
  178. package/test/redux/createMiddlewares.test.js +0 -96
  179. package/test/redux/index.test.js +0 -10
  180. package/test/redux/locationReducer.test.js +0 -39
  181. package/test/redux/middleware/createAddInputLocationBasePathMiddleware.test.js +0 -40
  182. package/test/redux/middleware/createRemoveOutputLocationBasePathMiddleware.test.js +0 -51
  183. package/test/redux/middleware/navigationOperationMiddleware.test.js +0 -78
  184. package/test/redux/middleware/parseInputLocationMiddleware.test.js +0 -62
  185. package/test/testUtil.js +0 -3
  186. package/types/data-storage/index.d.ts +0 -35
  187. package/types/redux/index.d.ts +0 -90
  188. package/types/scroll-position/index.d.ts +0 -107
  189. /package/lib/cjs/{session → environment}/lifecycle/InMemorySessionLifecycle.js +0 -0
  190. /package/lib/cjs/{session → environment}/lifecycle/WebBrowserSessionLifecycle.js +0 -0
  191. /package/lib/cjs/{session → environment}/lifecycle/page-lifecycle/PageLifecycle.js +0 -0
  192. /package/lib/cjs/{session → environment}/lifecycle/page-lifecycle/PageLifecycleInstance.js +0 -0
  193. /package/lib/cjs/{session → environment}/lifecycle/page-lifecycle/supportsConstructableEventTarget.js +0 -0
  194. /package/lib/cjs/{session → environment}/navigation/error/NavigationOutOfBoundsError.js +0 -0
  195. /package/lib/cjs/{session → environment}/navigation/operation/operations.js +0 -0
  196. /package/lib/esm/{session → environment}/lifecycle/InMemorySessionLifecycle.js +0 -0
  197. /package/lib/esm/{session → environment}/lifecycle/WebBrowserSessionLifecycle.js +0 -0
  198. /package/lib/esm/{session → environment}/lifecycle/page-lifecycle/PageLifecycle.js +0 -0
  199. /package/lib/esm/{session → environment}/lifecycle/page-lifecycle/PageLifecycleInstance.js +0 -0
  200. /package/lib/esm/{session → environment}/lifecycle/page-lifecycle/supportsConstructableEventTarget.js +0 -0
  201. /package/lib/esm/{session → environment}/navigation/error/NavigationOutOfBoundsError.js +0 -0
  202. /package/lib/esm/{session → environment}/navigation/operation/operations.js +0 -0
  203. /package/src/{session → environment}/lifecycle/InMemorySessionLifecycle.js +0 -0
  204. /package/src/{session → environment}/lifecycle/WebBrowserSessionLifecycle.js +0 -0
  205. /package/src/{session → environment}/lifecycle/page-lifecycle/PageLifecycle.js +0 -0
  206. /package/src/{session → environment}/lifecycle/page-lifecycle/PageLifecycleInstance.js +0 -0
  207. /package/src/{session → environment}/lifecycle/page-lifecycle/supportsConstructableEventTarget.js +0 -0
  208. /package/src/{session → environment}/navigation/error/NavigationOutOfBoundsError.js +0 -0
  209. /package/src/{session → environment}/navigation/operation/operations.js +0 -0
  210. /package/test/{parseInputLocationMiddleware.test.js → parseInputLocation.test.js} +0 -0
@@ -2,86 +2,175 @@
2
2
 
3
3
  exports.__esModule = true;
4
4
  exports.default = void 0;
5
- var _redux = require("redux");
6
- var _Actions = _interopRequireDefault(require("./redux/Actions"));
7
- var _createMiddlewares = _interopRequireDefault(require("./redux/createMiddlewares"));
8
- var _locationReducer = _interopRequireDefault(require("./redux/locationReducer"));
5
+ var _basePath = require("./basePath");
6
+ var _LocationDataStorage = _interopRequireDefault(require("./data-storage/LocationDataStorage"));
7
+ var _getLocationFromInternalLocation = _interopRequireDefault(require("./getLocationFromInternalLocation"));
8
+ var _isPromise = _interopRequireDefault(require("./isPromise"));
9
+ var _navigationBlockers = require("./navigationBlockers");
10
+ var _navigationBlockersEvaluation = require("./navigationBlockersEvaluation");
11
+ var _parseInputLocation = _interopRequireDefault(require("./parseInputLocation"));
9
12
  var _ScrollPositionRestoration = _interopRequireDefault(require("./scroll-position/ScrollPositionRestoration"));
10
- const _excluded = ["maintainScrollPosition"];
13
+ var _Session = _interopRequireDefault(require("./session/Session"));
11
14
  function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
12
- 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; }
13
- function getCreateMiddlewaresOptions(navigationStackOptions) {
14
- if (!navigationStackOptions) {
15
- return undefined;
16
- }
17
- // eslint-disable-next-line no-unused-vars
18
- const restOptions = _objectWithoutPropertiesLoose(navigationStackOptions, _excluded);
19
- return restOptions;
20
- }
21
15
  class NavigationStack {
22
- constructor(session, options) {
23
- this._session = session;
16
+ constructor(Environment, {
17
+ basePath,
18
+ manageScrollPosition,
19
+ scrollPositionSetter
20
+ } = {}) {
21
+ // Allows temporarily ignoring location update events.
22
+ this._doAndIgnoreLocationUpdates = func => {
23
+ this._ignoreLocationUpdates = true;
24
+ func();
25
+ this._ignoreLocationUpdates = false;
26
+ };
27
+ // Create a session.
28
+ this._session = new _Session.default(Environment);
29
+
30
+ // Base path, if used.
31
+ this._basePath = basePath;
32
+
33
+ // Create location data storage.
34
+ this.dataStorage = new _LocationDataStorage.default(this._session, {
35
+ namespace: 'navigation-stack'
36
+ });
37
+
38
+ // Allows temporarily ignoring location update events when set to `true`.
39
+ this._ignoreLocationUpdates = false;
24
40
 
25
- // Create a Redux store.
26
- this._store = (0, _redux.createStore)(_locationReducer.default, (0, _redux.applyMiddleware)(...(0, _createMiddlewares.default)(session, getCreateMiddlewaresOptions(options))));
41
+ // Subscribe to location updates.
42
+ // * Ignores location updates if `_ignoreLocationUpdates` flag is temporarily set.
43
+ // * Runs navigation blockers to see if the location update should be reverted.
44
+ // * Updates `this._location` if the update wasn't ignored or blocked.
45
+ this._unsubscribe = this._session.subscribe(location => {
46
+ // If this location update shouldn't be temporarily ignored.
47
+ if (!this._ignoreLocationUpdates) {
48
+ // Remove `basePath` from `location`.
49
+ location = (0, _basePath.removeBasePath)(location, this._basePath);
50
+
51
+ // See if the location update should've been blocked.
52
+ // If it should've, it will automatically "rewind" it.
53
+ const result = (0, _navigationBlockersEvaluation.blockNonProgrammaticNavigationIfRequired)(location, this._session, this._doAndIgnoreLocationUpdates);
54
+ const onResult = blocked => {
55
+ if (!blocked) {
56
+ // Update `this._location`.
57
+ // Since it's gonna be returned from the public `this.current()` method,
58
+ // convert it from `LocationInternal` to `Location`.
59
+ this._location = (0, _getLocationFromInternalLocation.default)(location);
60
+ }
61
+ };
62
+ if ((0, _isPromise.default)(result)) {
63
+ result.then(onResult);
64
+ } else {
65
+ onResult(result);
66
+ }
67
+ }
68
+ });
27
69
 
28
70
  // Create `ScrollPositionRestoration`.
29
- if (options && options.maintainScrollPosition) {
30
- this._scrollPositionRestoration = new _ScrollPositionRestoration.default(session);
71
+ if (manageScrollPosition) {
72
+ this._scrollPositionRestoration = new _ScrollPositionRestoration.default(this._session,
73
+ // Custom `ScrollPositionSetter`.
74
+ {
75
+ scrollPositionSetter
76
+ });
31
77
  }
32
78
  }
79
+
80
+ // Subscribes to any changes of the current location.
81
+ // The first subscriber is always the `NavigationStack` itself
82
+ // because its listener is what drives the actual navigation.
83
+ // Any additional application-specific listeners could be added, if required.
84
+ subscribe(listener) {
85
+ // `NavigationStack.subscribe()` is simply a proxy to `Session.subscribe()`
86
+ // with the only convenience feature that it "normalizes" the `location` argument.
87
+ return this._session.subscribe(locationInternal => {
88
+ listener((0, _getLocationFromInternalLocation.default)(locationInternal));
89
+ });
90
+ }
91
+ addNavigationBlocker(blocker) {
92
+ return (0, _navigationBlockers.addNavigationBlocker)(this._session, blocker);
93
+ }
33
94
  addScrollableContainer(scrollableContainerKey, scrollableContainer) {
34
95
  if (!this._scrollPositionRestoration) {
35
- throw new Error('`maintainScrollPosition: true` option not passed');
96
+ throw new Error('`manageScrollPosition: true` option not passed');
36
97
  }
37
98
  return this._scrollPositionRestoration.addScrollableContainer(scrollableContainerKey, scrollableContainer);
38
99
  }
39
- subscribe(listener) {
40
- // Subscribe to any potential Redux state changes.
41
- return this._store.subscribe(() => {
42
- // Initially, calls the listener when setting the initial location.
43
- // After that, calls it on any location change.
44
- const location = this.current();
45
- if (!this._latestLocation || location !== this._latestLocation) {
46
- this._latestLocation = location;
47
- listener(location);
48
- }
49
- });
50
- }
51
100
  init(initialLocation) {
52
- if (this._latestLocation) {
101
+ if (this._location) {
53
102
  throw new Error('Already initialized');
54
103
  }
55
- this._store.dispatch(_Actions.default.init(initialLocation));
56
- this._latestLocation = this.current();
104
+ this._session.start(initialLocation && this._parseInputLocation(initialLocation));
105
+ if (this._scrollPositionRestoration) {
106
+ this._scrollPositionRestoration.start();
107
+ }
57
108
  }
58
109
  current() {
59
- return this._store.getState();
110
+ // TypeScript definition of the `.current()` method tells that it always returns
111
+ // some non-`undefined` location.
112
+ // But `this._location` is `undefined` until `.init(initialLocation?)` is called.
113
+ // To work around that limitation, it simply throws if `.current()` is called before `.init()`.
114
+ if (!this._location) {
115
+ throw new Error('Not initialized');
116
+ }
117
+ return this._location;
60
118
  }
61
119
  push(location) {
62
- this._store.dispatch(_Actions.default.push(location));
120
+ this._navigate('push', location);
63
121
  }
64
122
  replace(location) {
65
- this._store.dispatch(_Actions.default.replace(location));
123
+ this._navigate('replace', location);
124
+ }
125
+ _navigate(operation, location) {
126
+ const toLocation = this._parseInputLocation(location);
127
+ const result = (0, _navigationBlockersEvaluation.blockProgrammaticNavigationIfRequired)(toLocation, this._session);
128
+ const onResult = blocked => {
129
+ if (!blocked) {
130
+ this._session.navigate(operation, toLocation);
131
+ }
132
+ };
133
+ if ((0, _isPromise.default)(result)) {
134
+ result.then(onResult);
135
+ } else {
136
+ onResult(result);
137
+ }
66
138
  }
67
139
  shift(delta) {
68
- this._store.dispatch(_Actions.default.shift(delta));
140
+ this._session.shift(delta);
69
141
  }
70
142
  stop() {
143
+ if (!this._unsubscribe) {
144
+ throw new Error('Already stopped');
145
+ }
146
+ this._unsubscribe();
147
+ this._unsubscribe = undefined;
148
+
149
+ // Even if it calls `unsubscribe()` function above, any other subscriptions
150
+ // would still stay. We're not talking about `navigationStack.subscribe()`
151
+ // subscriptions because those don't really matter in terms of cleaning them up:
152
+ // those're just Redux store subscriptions that don't have any side effects.
153
+ // Subscriptions we're talking here are `Session`'s own subscription
154
+ // via `session.subscribe()` and any hypothetical manual `session.subscribe()`
155
+ // calls that could be made by the application code for whatever purpose.
156
+ // Both of those should be cleared.
157
+ // To work around that, `.stop()` function removes all subscriptions.
158
+ this._session.stop();
159
+ (0, _navigationBlockers.removeAllNavigationBlockers)(this._session);
71
160
  if (this._scrollPositionRestoration) {
72
161
  this._scrollPositionRestoration.stop();
73
162
  }
74
- this._store.dispatch(_Actions.default.stop());
75
163
  }
76
- locationRendered() {
77
- if (this._scrollPositionRestoration) {
78
- const location = this.current();
79
- if (!location) {
80
- throw new Error('Not initialized');
81
- }
82
- return this._scrollPositionRestoration.locationRendered(location);
164
+ locationRendered(location) {
165
+ if (!this._scrollPositionRestoration) {
166
+ throw new Error('`manageScrollPosition: true` option not passed');
83
167
  }
84
- return Promise.resolve();
168
+ return this._scrollPositionRestoration.locationRendered(location);
169
+ }
170
+ _parseInputLocation(inputLocation) {
171
+ // Parse input location (string or incomplete object) to a proper `location` object.
172
+ // Add `basePath` to `location`.
173
+ return (0, _basePath.addBasePath)((0, _parseInputLocation.default)(inputLocation), this._basePath);
85
174
  }
86
175
  }
87
176
  exports.default = NavigationStack;
@@ -10,6 +10,7 @@ class DataStorage {
10
10
  throw new Error('`DataStorage` requires a `session.key`');
11
11
  }
12
12
  this._sessionKey = session.key;
13
+ this._log = session.environment.log;
13
14
  this._dataStorage = session.environment.dataStorage;
14
15
  this._namespace = namespace;
15
16
  }
@@ -26,8 +27,8 @@ class DataStorage {
26
27
  // junk into sessionStorage under our namespace.
27
28
  return JSON.parse(value);
28
29
  } catch (error) {
29
- // eslint-disable-next-line no-console
30
- console.error('[navigation-stack] Could not read data from storage');
30
+ this._log.error('[navigation-stack] Could not read data from storage');
31
+ this._log.error(error);
31
32
 
32
33
  // Pretend that the entry doesn't exist.
33
34
  return undefined;
@@ -40,8 +41,8 @@ class DataStorage {
40
41
  this._dataStorage.remove(storageKey);
41
42
  } catch (error) {
42
43
  // No need to handle errors here.
43
- // eslint-disable-next-line no-console
44
- console.error('[navigation-stack] Could not delete data from storage');
44
+ this._log.error('[navigation-stack] Could not delete data from storage');
45
+ this._log.error(error);
45
46
  }
46
47
  return;
47
48
  }
@@ -54,8 +55,8 @@ class DataStorage {
54
55
  } catch (error) {
55
56
  // No need to handle errors here either. If it didn't work, it didn't
56
57
  // work. We make no guarantees about actually saving the value.
57
- // eslint-disable-next-line no-console
58
- console.error('[navigation-stack] Could not save data in storage');
58
+ this._log.error('[navigation-stack] Could not save data in storage');
59
+ this._log.error(error);
59
60
  }
60
61
  }
61
62
 
@@ -3,11 +3,17 @@
3
3
  exports.__esModule = true;
4
4
  exports.default = void 0;
5
5
  var _InMemoryDataStorage = _interopRequireDefault(require("./data-storage/InMemoryDataStorage"));
6
+ var _InMemorySessionLifecycle = _interopRequireDefault(require("./lifecycle/InMemorySessionLifecycle"));
7
+ var _InMemoryLog = _interopRequireDefault(require("./log/InMemoryLog"));
8
+ var _InMemoryNavigation = _interopRequireDefault(require("./navigation/InMemoryNavigation"));
6
9
  var _InMemoryScrollPosition = _interopRequireDefault(require("./scroll-position/InMemoryScrollPosition"));
7
10
  function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
8
11
  class InMemoryEnvironment {
9
12
  constructor() {
10
13
  this.dataStorage = new _InMemoryDataStorage.default();
14
+ this.log = new _InMemoryLog.default();
15
+ this.lifecycle = new _InMemorySessionLifecycle.default();
16
+ this.navigation = new _InMemoryNavigation.default();
11
17
  this.scrollPosition = new _InMemoryScrollPosition.default();
12
18
  }
13
19
  }
@@ -2,16 +2,15 @@
2
2
 
3
3
  exports.__esModule = true;
4
4
  exports.default = void 0;
5
- var _InMemorySession = _interopRequireDefault(require("./InMemorySession"));
5
+ var _InMemoryEnvironment = _interopRequireDefault(require("./InMemoryEnvironment"));
6
6
  var _ServerSideNavigation = _interopRequireDefault(require("./navigation/ServerSideNavigation"));
7
7
  function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
8
8
  // `ServerSideRenderSession` is just a `InMemorySession` that specifically prohibits any navigation.
9
- class ServerSideRenderSession extends _InMemorySession.default {
9
+ class ServerSideRenderEnvironment extends _InMemoryEnvironment.default {
10
10
  constructor() {
11
- super({
12
- navigation: new _ServerSideNavigation.default()
13
- });
11
+ super();
12
+ this.navigation = new _ServerSideNavigation.default();
14
13
  }
15
14
  }
16
- exports.default = ServerSideRenderSession;
15
+ exports.default = ServerSideRenderEnvironment;
17
16
  module.exports = exports.default;
@@ -3,12 +3,18 @@
3
3
  exports.__esModule = true;
4
4
  exports.default = void 0;
5
5
  var _WebBrowserDataStorage = _interopRequireDefault(require("./data-storage/WebBrowserDataStorage"));
6
+ var _WebBrowserSessionLifecycle = _interopRequireDefault(require("./lifecycle/WebBrowserSessionLifecycle"));
7
+ var _WebBrowserLog = _interopRequireDefault(require("./log/WebBrowserLog"));
8
+ var _WebBrowserNavigation = _interopRequireDefault(require("./navigation/WebBrowserNavigation"));
6
9
  var _WebBrowserScrollPosition = _interopRequireDefault(require("./scroll-position/WebBrowserScrollPosition"));
7
10
  function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
8
11
  class WebBrowserEnvironment {
9
12
  constructor() {
10
13
  this.dataStorage = new _WebBrowserDataStorage.default();
11
14
  this.scrollPosition = new _WebBrowserScrollPosition.default();
15
+ this.lifecycle = new _WebBrowserSessionLifecycle.default();
16
+ this.log = new _WebBrowserLog.default();
17
+ this.navigation = new _WebBrowserNavigation.default();
12
18
  }
13
19
  }
14
20
  exports.default = WebBrowserEnvironment;
@@ -0,0 +1,23 @@
1
+ "use strict";
2
+
3
+ exports.__esModule = true;
4
+ exports.default = void 0;
5
+ const DEBUG_ENABLED = false;
6
+ class InMemoryLog {
7
+ debug(...args) {
8
+ if (DEBUG_ENABLED) {
9
+ // eslint-disable-next-line no-console
10
+ console.log(...args);
11
+ }
12
+ }
13
+ warn(...args) {
14
+ // eslint-disable-next-line no-console
15
+ console.warn(...args);
16
+ }
17
+ error(...args) {
18
+ // eslint-disable-next-line no-console
19
+ console.error(...args);
20
+ }
21
+ }
22
+ exports.default = InMemoryLog;
23
+ module.exports = exports.default;
@@ -0,0 +1,22 @@
1
+ "use strict";
2
+
3
+ exports.__esModule = true;
4
+ exports.default = void 0;
5
+ class WebBrowserLog {
6
+ debug(...args) {
7
+ if (window.NAVIGATION_STACK_DEBUG_ENABLED) {
8
+ // eslint-disable-next-line no-console
9
+ console.log(...args);
10
+ }
11
+ }
12
+ warn(...args) {
13
+ // eslint-disable-next-line no-console
14
+ console.warn(...args);
15
+ }
16
+ error(...args) {
17
+ // eslint-disable-next-line no-console
18
+ console.error(...args);
19
+ }
20
+ }
21
+ exports.default = WebBrowserLog;
22
+ module.exports = exports.default;
@@ -10,12 +10,23 @@ class InMemoryNavigation {
10
10
  this._stack = [];
11
11
  }
12
12
 
13
+ // Subscribes to any "asynchronous" changes of the current location,
14
+ // "asynchronous" changes being ones that happen out-of-sync with the code
15
+ // that might have potentially triggered those changes.
16
+ //
17
+ // For example, in a web browser, "Back"/"Forward" navigation happens out-of-sync
18
+ // with the code that calls `window.pushState()` or `window.replaceState()` function.
19
+ //
20
+ // Additionally, in a web browser, "Back"/"Forward" navigation could be triggered
21
+ // outside of the application code by user clicking those "Back"/"Forward" buttons manually
22
+ // in their web browser.
23
+ //
13
24
  // eslint-disable-next-line no-unused-vars
14
- subscribe(listener) {
15
- // `InMemoryNavigation` doesn't have any "asynchronycity" about it
16
- // and performs any navigation immediately at the time of the call.
17
- // Hence, no asynchronous listener would ever be called
18
- // due to no asynchronous events being dispatched.
25
+ subscribeToAsyncrhonousLocationUpdates(listener) {
26
+ // `InMemoryNavigation` location changes are always "synchronous"
27
+ // with the code that initiated such changes, i.e. it always performs
28
+ // any navigation immediately at the time such navigation is triggered in code.
29
+ // Hence, this function doesn't have to "subscribe" to anything, so it's a "no op".
19
30
  return () => {};
20
31
  }
21
32
  init(initialLocation, {
@@ -2,7 +2,7 @@
2
2
 
3
3
  exports.__esModule = true;
4
4
  exports.default = void 0;
5
- var _ServerSideNavigationError = _interopRequireDefault(require("./error/ServerSideNavigationError"));
5
+ var _ServerSideRedirectError = _interopRequireDefault(require("./error/ServerSideRedirectError"));
6
6
  function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
7
7
  /* eslint-disable no-underscore-dangle, max-classes-per-file */
8
8
 
@@ -21,12 +21,21 @@ class ServerSideNavigation {
21
21
  });
22
22
  }
23
23
 
24
+ // Subscribes to any "asynchronous" changes of the current location,
25
+ // "asynchronous" changes being ones that happen out-of-sync with the code
26
+ // that might have potentially triggered those changes.
27
+ //
28
+ // For example, in a web browser, "Back"/"Forward" navigation happens out-of-sync
29
+ // with the code that calls `window.pushState()` or `window.replaceState()` function.
30
+ //
31
+ // Additionally, in a web browser, "Back"/"Forward" navigation could be triggered
32
+ // outside of the application code by user clicking those "Back"/"Forward" buttons manually
33
+ // in their web browser.
34
+ //
24
35
  // eslint-disable-next-line no-unused-vars
25
- subscribe(listener) {
26
- // `ServerSideNavigation` doesn't have any "asynchronycity" about it
27
- // and any navigation is prohibited and would result in an error.
28
- // So no asynchronous listener would ever be called
29
- // due to no asynchronous events being dispatched.
36
+ subscribeToAsyncrhonousLocationUpdates(listener) {
37
+ // `ServerSideNavigation` location changes are prohibited, so they couldn't happen.
38
+ // Hence, this function doesn't have to "subscribe" to anything, so it's a "no op".
30
39
  return () => {};
31
40
  }
32
41
 
@@ -37,7 +46,7 @@ class ServerSideNavigation {
37
46
  index,
38
47
  delta
39
48
  }) {
40
- throw new _ServerSideNavigationError.default(location);
49
+ throw new _ServerSideRedirectError.default(location);
41
50
  }
42
51
 
43
52
  // eslint-disable-next-line no-unused-vars
@@ -53,15 +53,28 @@ class WebBrowserNavigation {
53
53
  this._currentLocationIndex = NO_LOCATION_INDEX;
54
54
  }
55
55
 
56
- // Subscribes to changes in the current location.
56
+ // Subscribes to any "asynchronous" changes of the current location,
57
+ // "asynchronous" changes being ones that happen out-of-sync with the code
58
+ // that might have potentially triggered those changes.
59
+ //
60
+ // For example, in a web browser, "Back"/"Forward" navigation happens out-of-sync
61
+ // with the code that calls `window.pushState()` or `window.replaceState()` function.
62
+ //
63
+ // Additionally, in a web browser, "Back"/"Forward" navigation could be triggered
64
+ // outside of the application code by user clicking those "Back"/"Forward" buttons manually
65
+ // in their web browser.
66
+ //
57
67
  // Returns an `unsubscribe()` function which is "idempotent", i.e. it can be called multiple times.
58
- subscribe(listener) {
68
+ //
69
+ subscribeToAsyncrhonousLocationUpdates(listener) {
59
70
  const onPopState = () => {
60
71
  // If "popstate" event is received before navigation is initialized,
61
- // ignore such "popstate" event. This behavior is logical from the application code's view.
72
+ // ignore such "popstate" event. Such "ignore" behavior is logical from
73
+ // the application code's point of view: it doesn't expect any navigation events
74
+ // to be recorded before it has initialized the navigation.
62
75
  // And besides, `this._currentLocationIndex` is not defined in such conditions.
63
76
  if (this._currentLocationIndex === NO_LOCATION_INDEX) {
64
- throw new Error('Received a "popstate" event before initialized');
77
+ throw new Error('Received a "popstate" event before finished initializing navigation');
65
78
  }
66
79
  const prevIndex = this._currentLocationIndex;
67
80
  const {
@@ -92,6 +105,16 @@ class WebBrowserNavigation {
92
105
  this._subscribed = false;
93
106
  };
94
107
  }
108
+
109
+ // When run in a web browser, it could not only "start" a new navigation session
110
+ // but also "resume" a previously-started navigation session. That could happen
111
+ // when the user refreshes a page in a web browser which still retains
112
+ // the previous navigation session's data but at the same time restarts
113
+ // the javascript code from scratch.
114
+ //
115
+ // So this `init()` method handles both cases: when there's previous navigation session's data
116
+ // that should be restored and when there's no previous navigation session's data.
117
+ //
95
118
  init(initialLocation, {
96
119
  operation,
97
120
  key,
@@ -108,7 +131,8 @@ class WebBrowserNavigation {
108
131
  // by calling `window.history.replaceState()` on page load.
109
132
  // Otherwise, `window.history.state` would be `null` for the initial location
110
133
  // and there'd be no place to store the additional properties of the initial location
111
- // such as `location.key`.
134
+ // such as `location.key`. Without `location.key` always being present the initial location object,
135
+ // there'd be a bug of incorrect scroll position being set on the initial location URL in some scenarios:
112
136
  // https://github.com/taion/scroll-behavior/issues/215
113
137
  //
114
138
  // If the user opens the initial page for the first time, `window.history.state` will be `null`.
@@ -122,7 +146,7 @@ class WebBrowserNavigation {
122
146
  index
123
147
  };
124
148
  // Call `history.replaceState()`.
125
- this._storeAdditionalPropertiesForLocation(initialLocation, additionalProperties, delta);
149
+ this._navigateToLocationAndKeepItsAdditionalPropertiesInHistory(initialLocation, additionalProperties, delta);
126
150
  }
127
151
  this._currentLocationIndex = index;
128
152
 
@@ -142,7 +166,7 @@ class WebBrowserNavigation {
142
166
  key,
143
167
  index
144
168
  };
145
- this._storeAdditionalPropertiesForLocation(location, additionalProperties, delta);
169
+ this._navigateToLocationAndKeepItsAdditionalPropertiesInHistory(location, additionalProperties, delta);
146
170
  this._currentLocationIndex = index;
147
171
 
148
172
  // Call the listeners.
@@ -204,7 +228,23 @@ class WebBrowserNavigation {
204
228
  delta
205
229
  };
206
230
  }
207
- _storeAdditionalPropertiesForLocation(location, additionalProperties, delta) {
231
+
232
+ // Stores "additional" properties associated with `location` in web browser's history storage.
233
+ // Web browser's history storage is not intended for large datasets and should only be used
234
+ // to store small bits of data.
235
+ //
236
+ // "Some browsers save state objects to the user's disk so they can be restored after the user restarts
237
+ // the browser, and impose a size limit on the serialized representation of a state object, and will throw
238
+ // an exception if you pass a state object whose serialized representation is larger than that size limit.
239
+ // So in cases where you want to ensure you have more space than what some browsers might impose,
240
+ // you're encouraged to use sessionStorage and/or localStorage."
241
+ //
242
+ // Source: https://developer.mozilla.org/en-US/docs/Web/API/History/pushState
243
+ //
244
+ // To store large amounts of data, one could use `window.sessionStorage` instead.
245
+ // It is accessible via `DataStorage(session)` class.
246
+ //
247
+ _navigateToLocationAndKeepItsAdditionalPropertiesInHistory(location, additionalProperties, delta) {
208
248
  const url = (0, _getLocationUrl.default)(location);
209
249
  // `delta` property is not stored in `window.history.state`
210
250
  // because it is supposed to be recalculated every time when reading from `window.history.state`.
@@ -6,7 +6,7 @@ var _getLocationUrl = _interopRequireDefault(require("../../../getLocationUrl"))
6
6
  const _excluded = ["operation"];
7
7
  function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
8
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 {
9
+ class ServerSideRedirectError extends Error {
10
10
  constructor(location) {
11
11
  super(location ? `Navigate to ${(0, _getLocationUrl.default)(location)}` : 'Navigate to previous or next location');
12
12
  if (location) {
@@ -17,5 +17,5 @@ class ServerSideNavigationError extends Error {
17
17
  }
18
18
  }
19
19
  }
20
- exports.default = ServerSideNavigationError;
20
+ exports.default = ServerSideRedirectError;
21
21
  module.exports = exports.default;
@@ -49,9 +49,24 @@ class WebBrowserScrollPosition {
49
49
  };
50
50
  }
51
51
  enableAutomaticScrollRestoration() {
52
+ // The default "auto" behavior seems to work in the following way:
53
+ //
54
+ // * It doesn't scroll to top on `window.history.pushState()` or `window.history.replaceState()`.
55
+ //
56
+ // * It does restore scroll position on "popstate" event
57
+ // (they say, in Firefox it happens before the event is dispatched,
58
+ // while in Chrome it happens after the event is dispatched)
59
+ //
60
+ // https://v5.reactrouter.com/web/guides/scroll-restoration
61
+ //
52
62
  window.history.scrollRestoration = 'auto';
53
63
  }
54
64
  disableAutomaticScrollRestoration() {
65
+ // Setting `window.history.scrollRestoration` value updates it in the current history entry
66
+ // and any subsequent history entries.
67
+ // This means that it should be set at application initialization stage,
68
+ // that is before any navigation.
69
+ // https://majido.github.io/scroll-restoration-proposal/history-based-api.html
55
70
  window.history.scrollRestoration = 'manual';
56
71
  }
57
72
  init() {}
@@ -0,0 +1,14 @@
1
+ "use strict";
2
+
3
+ exports.__esModule = true;
4
+ exports.default = getLocationBaseFromLocation;
5
+ const _excluded = ["key", "index"];
6
+ 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; }
7
+ // Converts `Location` object to a `LocationBase` object.
8
+ // It hides properties of location such as `key` or `index`.
9
+ function getLocationBaseFromLocation(location) {
10
+ // eslint-disable-next-line no-unused-vars
11
+ const locationBase = _objectWithoutPropertiesLoose(location, _excluded);
12
+ return locationBase;
13
+ }
14
+ module.exports = exports.default;
@@ -2,7 +2,8 @@
2
2
 
3
3
  exports.__esModule = true;
4
4
  exports.default = getLocationUrl;
5
- var _queryString = require("query-string");
5
+ var _stringifyQueryAsSearch = _interopRequireDefault(require("./stringifyQueryAsSearch"));
6
+ function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
6
7
  function getLocationUrl({
7
8
  pathname,
8
9
  search,
@@ -10,10 +11,7 @@ function getLocationUrl({
10
11
  hash
11
12
  }) {
12
13
  if (!search && query) {
13
- const queryString = (0, _queryString.stringify)(query);
14
- if (queryString) {
15
- search = `?${queryString}`;
16
- }
14
+ search = (0, _stringifyQueryAsSearch.default)(query);
17
15
  }
18
16
  return `${pathname}${search || ''}${hash || ''}`;
19
17
  }