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
@@ -1,235 +0,0 @@
1
- "use strict";
2
-
3
- exports.__esModule = true;
4
- exports.default = void 0;
5
- var _getLocationUrl = _interopRequireDefault(require("../getLocationUrl"));
6
- var _parseQueryFromSearch = _interopRequireDefault(require("../parseQueryFromSearch"));
7
- const _excluded = ["delta"];
8
- /* eslint-disable max-classes-per-file */
9
- function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
10
- 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; }
11
- const INITIAL_KEY_INDEX = -1;
12
- const INITIAL_INDEX = -1;
13
- const INIT_LOCATION_DELTA = 0;
14
-
15
- // A web browser has a notion of a "navigation history".
16
- // A "navigation history" exists within a given web browser's tab.
17
- // The user can click "Back" or "Forward" buttons in the web browser and it will automatically load
18
- // "previous" or "next" page from scratch.
19
- //
20
- // Later, web browsers added a `window.history` object that the application can,
21
- // but isn't required to, interact with. That `window.history` object allows the application
22
- // to programmatically control the URL in the address bar of the web browser, as well as
23
- // the "navigation history" by programmatically adding new entries to it or reading the current entry,
24
- // and it also allows the application to override the default web browser's behavior
25
- // when the user clicks "Back" or "Forward" buttons in the web browser.
26
- //
27
- // Specifically, the `window.history` object has a method called `.pushState()` which programmatically adds
28
- // a new entry in the "navigation history" and updates the URL in the address bar and also
29
- // tells the web browser that starting from the entry before this new entry in the "navigation history",
30
- // the application would prefer to manually handle any "Back"/"Forward" transition when the user clicks
31
- // those "Back" or "Forward" buttons in the web browser, and this behavior should persist for any future
32
- // "navigation history" entries programmatically added by the application via `window.history.pushState()`,
33
- // and will only stop if the user navigates from the page by the means of conventional navigation,
34
- // that is by clicking a standard hyperlink, at which point the current page gets "destroyed".
35
- //
36
- // So for manually "pushed" entries of the "navigation history", the web browser won't load those pages
37
- // from scratch after a user-initiated "Back" or "Forward" transition. In fact, it won't do anything and
38
- // it will just step aside and let the application itself do those transitions. The web browser will only
39
- // update the URL in the address bar and that's it.
40
- //
41
- // This whole thing allows the application to:
42
- //
43
- // * Load the "previous" or "next" page much faster than when using the default "from scratch" approach
44
- // because it doesn't have to destroy the current page, then send a new HTTP request to the server,
45
- // then parse the HTML response and initialize a new page, re-download all those images, etc.
46
- //
47
- // * Optionally render a snapshotted verison of the "previous" page thereby "restoring" the "previous" page
48
- // rather than reloading it from scratch, i.e. the state of the "previous" page could be fully restored.
49
- //
50
- class BrowserNavigation {
51
- constructor() {
52
- // `this._keyPrefix` exists to avoid `this._keyIndex` collision after a page refresh.
53
- // After a page refresh, `this._keyIndex` is reset to `0` while the previous navigation history
54
- // still exists because web browser navigation history survives a page reload.
55
- this._keyPrefix = Date.now().toString(36);
56
- // `this._keyIndex` is incremented every time the current location changes.
57
- this._keyIndex = INITIAL_KEY_INDEX;
58
-
59
- // `this._index` is the index of the top element in the navigation stack.
60
- // I.e. it's the index of the "current" location in the navigation stack.
61
- this._index = INITIAL_INDEX;
62
- }
63
- init() {
64
- return this._createEntryFromCurrentLocation('INIT');
65
- }
66
- _createEntryFromCurrentLocation(action) {
67
- const {
68
- pathname,
69
- search,
70
- hash
71
- } = window.location;
72
- const isSettingInitialLocation = this._index === INITIAL_INDEX;
73
- if (action === 'INIT' && !isSettingInitialLocation) {
74
- throw Error('Browser session has already been initialized');
75
- }
76
- if (isSettingInitialLocation && action !== 'INIT') {
77
- throw Error('Browser session must be initialized before reacting to location changes');
78
- }
79
- const {
80
- key,
81
- index,
82
- delta,
83
- state
84
- } = isSettingInitialLocation ? this._createAdditionalPropertiesForNewLocation({
85
- delta: 1,
86
- state: undefined
87
- }) : this._restoreAdditionalPropertiesForCurrentLocation();
88
- return {
89
- action,
90
- pathname,
91
- search,
92
- query: (0, _parseQueryFromSearch.default)(search),
93
- hash,
94
- key,
95
- index,
96
- delta: isSettingInitialLocation ? INIT_LOCATION_DELTA : delta,
97
- state
98
- };
99
- }
100
-
101
- // Subscribes to changes in location,
102
- // excluding ones that happened as a result of calling `.navigate()`.
103
- subscribe(listener) {
104
- const onPopState = () => {
105
- listener(this._createEntryFromCurrentLocation('SHIFT'));
106
- };
107
- window.addEventListener('popstate', onPopState);
108
- return () => {
109
- window.removeEventListener('popstate', onPopState);
110
- };
111
- }
112
- navigate(location) {
113
- const {
114
- action,
115
- state
116
- } = location;
117
- if (action !== 'PUSH' && action !== 'REPLACE') {
118
- throw Error(`Unrecognized browser session action: ${action}`);
119
- }
120
- if (this._index === INITIAL_INDEX) {
121
- throw Error('Browser session must be initialized before navigation');
122
- }
123
- const delta = action === 'PUSH' ? 1 : 0;
124
- const additionalProperties = this._createAdditionalPropertiesForNewLocation({
125
- delta,
126
- state
127
- });
128
- this._storeAdditionalPropertiesForLocation(location, additionalProperties);
129
- return Object.assign({}, location, additionalProperties);
130
- }
131
- shift(delta) {
132
- window.history.go(delta);
133
- }
134
- _createKeyForKeyIndex(keyIndex) {
135
- return `${this._keyPrefix}.${keyIndex.toString(36)}`;
136
- }
137
- _createAdditionalPropertiesForNewLocation({
138
- delta,
139
- state
140
- }) {
141
- this._keyIndex++;
142
- this._index += delta;
143
- return {
144
- key: this._createKeyForKeyIndex(this._keyIndex),
145
- index: this._index,
146
- delta,
147
- state
148
- };
149
- }
150
- _restoreAdditionalPropertiesForCurrentLocation() {
151
- // Initial location doesn't have any `window.history.state` assigned to it
152
- // because it wasn't navigated to via a `window.history.pushState()` method.
153
- // Because of that, the additional properties for the initial location can't be read
154
- // from `window.history.state` and have to be reconstructed manually.
155
- const {
156
- key,
157
- index,
158
- state
159
- } = window.history.state || this._getAdditionalPropertiesForInitialLocation();
160
- const delta = index - this._index;
161
- this._index = index;
162
- return {
163
- key,
164
- index,
165
- delta,
166
- state
167
- };
168
- }
169
- _storeAdditionalPropertiesForLocation(location, additionalProperties) {
170
- const url = (0, _getLocationUrl.default)(location);
171
- // `delta` property is not stored in `window.history.state`
172
- // because it is supposed to be recalculated every time when reading from `window.history.state`.
173
- const {
174
- delta
175
- } = additionalProperties,
176
- restProperties = _objectWithoutPropertiesLoose(additionalProperties, _excluded);
177
- if (delta === 1) {
178
- window.history.pushState(restProperties, null, url);
179
- } else if (delta === 0) {
180
- window.history.replaceState(restProperties, null, url);
181
- } else {
182
- throw new Error(`Unexpected \`delta\` when storing additional properties for location: ${delta}`);
183
- }
184
- }
185
-
186
- // Initial location doesn't have any `window.history.state` assigned to it
187
- // because it wasn't navigated to via a `window.history.pushState()` method.
188
- // Because of that, the additional properties for the initial location can't be read
189
- // from `window.history.state` and have to be reconstructed manually.
190
- _getAdditionalPropertiesForInitialLocation() {
191
- return {
192
- key: this._createKeyForKeyIndex(INITIAL_KEY_INDEX + 1),
193
- index: INITIAL_INDEX + 1,
194
- delta: INIT_LOCATION_DELTA,
195
- state: undefined
196
- };
197
- }
198
- }
199
- class BrowserDataStorage {
200
- // Returns either a `string` value or `null` if the key doesn't exist.
201
- get(key) {
202
- // `sessionStorage` persists across page reloads, and so does web browser navigation history.
203
- return window.sessionStorage.getItem(key);
204
- }
205
- remove(key) {
206
- // `sessionStorage` persists across page reloads, and so does web browser navigation history.
207
- window.sessionStorage.removeItem(key);
208
- }
209
- set(key, value) {
210
- // `sessionStorage` persists across page reloads, and so does web browser navigation history.
211
- window.sessionStorage.setItem(key, value);
212
- }
213
- }
214
- class BrowserSession {
215
- constructor() {
216
- this.navigation = new BrowserNavigation();
217
- this.dataStorage = new BrowserDataStorage();
218
- }
219
- addBeforeDestroyListener(onBeforeDestroy) {
220
- const onBeforeUnload = event => {
221
- if (onBeforeDestroy()) {
222
- // Calling `event.preventDefault()` will cause a web browser
223
- // to show a generic "Ok"/"Cancel" modal with some generic text:
224
- // "Are you sure to leave the current page?".
225
- event.preventDefault();
226
- }
227
- };
228
- window.addEventListener('beforeunload', onBeforeUnload);
229
- return () => {
230
- window.removeEventListener('beforeunload', onBeforeUnload);
231
- };
232
- }
233
- }
234
- exports.default = BrowserSession;
235
- module.exports = exports.default;
@@ -1,223 +0,0 @@
1
- "use strict";
2
-
3
- exports.__esModule = true;
4
- exports.default = void 0;
5
- var _normalizeInputLocation = _interopRequireDefault(require("../normalizeInputLocation"));
6
- function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
7
- /* eslint-disable max-classes-per-file */
8
-
9
- // eslint-disable-next-line no-underscore-dangle
10
- function _loadState(load, isValidLoadedData) {
11
- try {
12
- const data = JSON.parse(load());
13
-
14
- // Check that the stack and index at least seem reasonable before using
15
- // them as state. This isn't foolproof, but it might prevent mistakes.
16
- // Also perform a basic validation of `state`.
17
- if (isValidLoadedData(data)) {
18
- return data;
19
- }
20
- } catch (error) {} // eslint-disable-line no-empty
21
-
22
- return null;
23
- }
24
-
25
- // eslint-disable-next-line no-underscore-dangle
26
- function _saveState(save, data) {
27
- try {
28
- save(JSON.stringify(data));
29
- } catch (error) {} // eslint-disable-line no-empty
30
- }
31
- class MemoryNavigation {
32
- constructor(initialLocation, {
33
- save,
34
- load
35
- } = {}) {
36
- this._save = save;
37
- this._keyPrefix = Date.now().toString(36);
38
- this._keyIndex = 0;
39
- this._subscriptionListener = null;
40
- const initialState = load ? _loadState(load, this._isValidLoadedData) : null;
41
- if (initialState) {
42
- this._stack = initialState.stack;
43
- this._index = initialState.index;
44
- } else {
45
- this._stack = [Object.assign({}, (0, _normalizeInputLocation.default)(initialLocation), {
46
- key: this._getNextKey()
47
- })];
48
- this._index = 0;
49
- }
50
- }
51
- _isValidLoadedData({
52
- stack,
53
- index
54
- }) {
55
- // Check that the `stack` and `index` at least seem reasonable before using them.
56
- // This isn't foolproof, but it might prevent mistakes.
57
- return Array.isArray(stack) && typeof index === 'number' && stack[index];
58
- }
59
- init() {
60
- return this._createLocationObject({
61
- action: 'INIT',
62
- delta: 0
63
- });
64
- }
65
- subscribe(listener) {
66
- this._subscriptionListener = listener;
67
- return () => {
68
- this._subscriptionListener = null;
69
- };
70
- }
71
- navigate(location) {
72
- const {
73
- action,
74
- pathname,
75
- search,
76
- query,
77
- hash,
78
- state
79
- } = location;
80
- if (action !== 'PUSH' && action !== 'REPLACE') {
81
- throw Error(`Unrecognized browser session action: ${action}`);
82
- }
83
- const delta = action === 'PUSH' ? 1 : 0;
84
- this._index += delta;
85
- const key = this._getNextKey();
86
- this._stack[this._index] = {
87
- pathname,
88
- search,
89
- query,
90
- hash,
91
- state,
92
- key
93
- };
94
- if (action === 'PUSH') {
95
- this._stack.length = this._index + 1;
96
- }
97
- if (this._save) {
98
- _saveState(this._save, {
99
- stack: this._stack,
100
- index: this._index
101
- });
102
- }
103
- return Object.assign({}, location, {
104
- key,
105
- index: this._index,
106
- delta
107
- });
108
- }
109
- shift(delta) {
110
- const prevIndex = this._index;
111
- this._index = Math.min(Math.max(this._index + delta, 0), this._stack.length - 1);
112
- if (this._index === prevIndex) {
113
- return;
114
- }
115
- if (this._save) {
116
- _saveState(this._save, {
117
- stack: this._stack,
118
- index: this._index
119
- });
120
- }
121
- if (this._subscriptionListener) {
122
- this._subscriptionListener(this._createLocationObject({
123
- action: 'SHIFT',
124
- delta: this._index - prevIndex
125
- }));
126
- }
127
- }
128
- _getNextKey() {
129
- const key = `${this._keyPrefix}.${this._keyIndex.toString(36)}`;
130
- this._keyIndex++;
131
- return key;
132
- }
133
- _createLocationObject({
134
- action,
135
- delta
136
- }) {
137
- return Object.assign({}, this._stack[this._index], {
138
- action,
139
- index: this._index,
140
- delta
141
- });
142
- }
143
- }
144
- class MemoryDataStorage {
145
- constructor({
146
- load,
147
- save
148
- } = {}) {
149
- this._save = save;
150
- const initialState = load ? _loadState(load, this._isValidLoadedData) : null;
151
- if (initialState) {
152
- this._state = initialState.state;
153
- } else {
154
- this._state = {};
155
- }
156
- }
157
-
158
- // Returns either a `string` value or `null` if the key doesn't exist.
159
- get(key) {
160
- if (key in this._state) {
161
- return this._state[key];
162
- }
163
- return null;
164
- }
165
- remove(key) {
166
- if (key in this._state) {
167
- delete this._state[key];
168
- }
169
- if (this._save) {
170
- _saveState(this._save, {
171
- state: this._state
172
- });
173
- }
174
- }
175
- set(key, value) {
176
- this._state[key] = value;
177
- if (this._save) {
178
- _saveState(this._save, {
179
- state: this._state
180
- });
181
- }
182
- }
183
- _isValidLoadedData({
184
- state
185
- }) {
186
- // Perform a basic validation of `state`.
187
- return typeof state === 'object' && state !== null;
188
- }
189
- }
190
- function createNestedStateSaveLoadFunctions({
191
- save,
192
- load
193
- }, key) {
194
- return {
195
- save: save ? data => save(key, data) : undefined,
196
- load: load ? () => load(key) : undefined
197
- };
198
- }
199
- class MemorySession {
200
- constructor(initialLocation, {
201
- save,
202
- load
203
- } = {}) {
204
- this.navigation = new MemoryNavigation(initialLocation, createNestedStateSaveLoadFunctions({
205
- save,
206
- load
207
- }, 'navigation'));
208
- this.dataStorage = new MemoryDataStorage(createNestedStateSaveLoadFunctions({
209
- save,
210
- load
211
- }, 'dataStorage'));
212
- }
213
-
214
- // "Before destroy" listeners are currently ignored.
215
- // If required, one could implement a `_destroy()` method
216
- // and there check that the listeners actually do get called.
217
- // eslint-disable-next-line no-unused-vars
218
- addBeforeDestroyListener(listener) {
219
- return () => {};
220
- }
221
- }
222
- exports.default = MemorySession;
223
- module.exports = exports.default;
@@ -1,65 +0,0 @@
1
- "use strict";
2
-
3
- exports.__esModule = true;
4
- exports.default = void 0;
5
- var _normalizeInputLocation = _interopRequireDefault(require("../normalizeInputLocation"));
6
- function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
7
- /* eslint-disable max-classes-per-file */
8
-
9
- function noop() {}
10
- function serverSideNavigationNotPossible() {
11
- throw new Error('Server-side navigation is not possible');
12
- }
13
- class ServerNavigation {
14
- constructor(initialLocation) {
15
- this._location = (0, _normalizeInputLocation.default)(initialLocation);
16
- }
17
- init() {
18
- return Object.assign({
19
- action: 'INIT'
20
- }, this._location, {
21
- index: 0,
22
- key: '0'
23
- });
24
- }
25
- subscribe() {
26
- // Server-side environment emits no location subscription events.
27
- return noop;
28
- }
29
-
30
- // Navigation methods are not implemented, because `ServerSession` instances
31
- // cannot navigate.
32
- navigate() {
33
- serverSideNavigationNotPossible();
34
- }
35
-
36
- // Navigation methods are not implemented, because `ServerSession` instances
37
- // cannot navigate.
38
- shift() {
39
- serverSideNavigationNotPossible();
40
- }
41
- }
42
- class ServerDataStorage {
43
- // It doesn't seem to make any sense to store anything on server side.
44
- // Hence, state management methods are "no op" stubs.
45
- get() {
46
- return null;
47
- }
48
- remove() {}
49
- set() {}
50
- }
51
- class ServerSession {
52
- constructor(initialLocation) {
53
- this.navigation = new ServerNavigation(initialLocation);
54
- this.dataStorage = new ServerDataStorage();
55
- }
56
-
57
- // "Before destroy" listeners are currently ignored.
58
- // If required, one could implement a `_destroy()` method
59
- // and there check that the listeners actually do get called.
60
- addBeforeDestroyListener() {
61
- return noop;
62
- }
63
- }
64
- exports.default = ServerSession;
65
- module.exports = exports.default;
@@ -1,53 +0,0 @@
1
- import getLocationUrl from './getLocationUrl';
2
- export default class LocationDataStorage {
3
- constructor(environment, {
4
- namespace
5
- } = {}) {
6
- this._environment = environment;
7
- this._getFallbackLocationKey = getLocationUrl;
8
- this._stateKeyPrefix = namespace ? `${namespace}|` : '';
9
- }
10
- get(location, key) {
11
- const stateKey = this._getStateKey(location, key);
12
- try {
13
- const value = this._environment.dataStorage.get(stateKey);
14
- // === null is probably sufficient.
15
- if (value === null) {
16
- return undefined;
17
- }
18
-
19
- // We want to catch JSON parse errors in case someone separately threw
20
- // junk into sessionStorage under our namespace.
21
- return JSON.parse(value);
22
- } catch (error) {
23
- // Pretend that the entry doesn't exist.
24
- return undefined;
25
- }
26
- }
27
- set(location, key, value) {
28
- const stateKey = this._getStateKey(location, key);
29
- if (value === undefined) {
30
- try {
31
- this._environment.dataStorage.remove(stateKey);
32
- } catch (error) {
33
- // No need to handle errors here.
34
- }
35
- return;
36
- }
37
-
38
- // Unlike with read, we want to fail on invalid values here, since the
39
- // value here is provided by the caller of this method.
40
- const valueString = JSON.stringify(value);
41
- try {
42
- this._environment.dataStorage.set(stateKey, valueString);
43
- } catch (error) {
44
- // No need to handle errors here either. If it didn't work, it didn't
45
- // work. We make no guarantees about actually saving the value.
46
- }
47
- }
48
- _getStateKey(location, key) {
49
- const locationKey = location.key || this._getFallbackLocationKey(location);
50
- const keyPrefix = `${this._stateKeyPrefix}${locationKey}`;
51
- return `${keyPrefix}|${key}`;
52
- }
53
- }
@@ -1,2 +0,0 @@
1
- // eslint-disable-next-line no-restricted-exports
2
- export { addBeforeLocationChangeListener as default } from './beforeLocationChangeListeners';
@@ -1,44 +0,0 @@
1
- /* eslint-disable no-underscore-dangle */
2
-
3
- export function getBeforeLocationChangeListeners(session) {
4
- return session._beforeLocationChangeListenersList || [];
5
- }
6
- function addBeforeLocationChangeListenerToTheList(listener, session) {
7
- if (!session._beforeLocationChangeListenersList) {
8
- session._beforeLocationChangeListenersList = [];
9
- }
10
- session._beforeLocationChangeListenersList.push(listener);
11
- }
12
- function removeBeforeLocationChangeListenerFromTheList(listener, session) {
13
- if (session._beforeLocationChangeListenersList) {
14
- session._beforeLocationChangeListenersList = session._beforeLocationChangeListenersList.filter(_ => _ !== listener);
15
- }
16
- }
17
- export function removeAllBeforeLocationChangeListeners(session) {
18
- session._beforeLocationChangeListenersList = [];
19
- }
20
-
21
- // Runs the `listener` while ignoring any errors that might be thrown by it.
22
- function runBeforeLocationChangeListener(listener, location) {
23
- try {
24
- listener(location);
25
- } catch (error) {
26
- // eslint-disable-next-line no-console
27
- console.warn(`Ignoring before location change listener \`${listener.name}\` that failed with \`${error}\`.`);
28
- // eslint-disable-next-line no-console
29
- console.error(error);
30
- }
31
- }
32
-
33
- // Runs all listeners in order.
34
- export function runBeforeLocationChangeListeners(navigationListeners, toLocation) {
35
- for (const listener of navigationListeners) {
36
- runBeforeLocationChangeListener(listener, toLocation);
37
- }
38
- }
39
- export function addBeforeLocationChangeListener(session, listener) {
40
- addBeforeLocationChangeListenerToTheList(listener, session);
41
- return () => {
42
- removeBeforeLocationChangeListenerFromTheList(listener, session);
43
- };
44
- }
@@ -1,41 +0,0 @@
1
- import createBasePathMiddleware from './middleware/createBasePathMiddleware';
2
- import createBeforeLocationChangeListenerMiddleware from './middleware/createBeforeLocationChangeListenerMiddleware';
3
- import createLocationMiddleware from './middleware/createLocationMiddleware';
4
- import createNavigationBlockerMiddleware from './middleware/createNavigationBlockerMiddleware';
5
- import navigationActionMiddleware from './middleware/navigationActionMiddleware';
6
- import normalizeInputLocationMiddleware from './middleware/normalizeInputLocationMiddleware';
7
- export default function createMiddlewares(session, options) {
8
- // Allows temporarily ignoring location update events.
9
- let shouldIgnoreLocationSubscriptionEvents = false;
10
- const ignoreLocationSubscriptionEvents = func => {
11
- shouldIgnoreLocationSubscriptionEvents = true;
12
- func();
13
- shouldIgnoreLocationSubscriptionEvents = false;
14
- };
15
- return [
16
- // Validates that the action "payload" (input location) is a proper `NormalizedInputLocation`.
17
- normalizeInputLocationMiddleware,
18
- // Transforms a "PUSH" / "REPLACE" action into a "NAVIGATE" action.
19
- navigationActionMiddleware,
20
- // If a website is hosted under a certain path (`basePath`)
21
- // then this middleware will automatically strip that starting segment from the `pathname` of `location`s.
22
- createBasePathMiddleware(options && options.basePath),
23
- // Allows blocking navigation.
24
- // Handles `NAVIGATE` actions dispatched by the application itself.
25
- createNavigationBlockerMiddleware(session, {
26
- ignoreLocationSubscriptionEvents
27
- }),
28
- // This "middleware" performs the actual navigation according to the `session` being used.
29
- // For example, when `BrowserSession` is used, it calls methods of the `history` object.
30
- createLocationMiddleware(session, {
31
- shouldIgnoreLocationSubscriptionEvents: () => shouldIgnoreLocationSubscriptionEvents
32
- }),
33
- // Allows blocking navigation.
34
- // Handles location `UPDATE` actions dispatched in response to location update events.
35
- createNavigationBlockerMiddleware(session, {
36
- ignoreLocationSubscriptionEvents
37
- }),
38
- // Allows subscribing to upcoming location changes
39
- // before those changes are applied in the `location` object in the state.
40
- createBeforeLocationChangeListenerMiddleware(session)];
41
- }
@@ -1,19 +0,0 @@
1
- import { addBasePath, removeBasePath } from '../basePath';
2
- import createTransformLocationMiddleware from './createTransformLocationMiddleware';
3
-
4
- // Creates a "middleware" that, when a website is hosted under a certain path (`basePath`),
5
- // automatically strips that starting segment from the `pathname` of `location`s.
6
- export default function createBasePathMiddleware(basePath) {
7
- return createTransformLocationMiddleware({
8
- // Transforms input `Location`:
9
- // prepends `basePath` to the URL.
10
- transformInputLocation: location => {
11
- return addBasePath(location, basePath);
12
- },
13
- // Transforms subscription `Location` object:
14
- // removes `basePath` from the URL.
15
- transformSubscriptionLocation: location => {
16
- return removeBasePath(location, basePath);
17
- }
18
- });
19
- }