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
package/package.json CHANGED
@@ -1,12 +1,13 @@
1
1
  {
2
2
  "name": "navigation-stack",
3
- "version": "0.3.0",
3
+ "version": "0.4.0",
4
4
  "description": "Handles navigation in a web browser",
5
5
  "keywords": [
6
6
  "history",
7
7
  "browser",
8
8
  "navigation",
9
- "router"
9
+ "router",
10
+ "routing"
10
11
  ],
11
12
  "homepage": "https://gitlab.com/catamphetamine/navigation-stack#readme",
12
13
  "bugs": {
@@ -24,7 +25,7 @@
24
25
  "scripts": {
25
26
  "build": "rimraf lib && 4c build --types false src && npm run build:pick && npm run build:types",
26
27
  "build:pick": "cherry-pick --cjs-dir cjs --esm-dir esm --cwd lib ../src",
27
- "build:types": "cpy types/*.d.ts lib",
28
+ "build:types": "cpy types/*.d.ts lib && cpy types/data-storage/*.d.ts lib/data-storage && cpy types/scroll-position/*.d.ts lib/scroll-position && cpy types/redux/*.d.ts lib/redux",
28
29
  "format": "4c format --prettier-ignore .eslintignore .",
29
30
  "lint": "4c lint --prettier-ignore .eslintignore .",
30
31
  "prepublishOnly": "npm run build",
@@ -44,7 +45,8 @@
44
45
  },
45
46
  "prettier": "@4c/prettier-config",
46
47
  "dependencies": {
47
- "query-string": "^5.1.1"
48
+ "query-string": "^5.1.1",
49
+ "redux": "^5.0.1"
48
50
  },
49
51
  "devDependencies": {
50
52
  "@4c/babel-preset": "^9.1.0",
@@ -65,6 +67,7 @@
65
67
  "delay": "^4.4.1",
66
68
  "dirty-chai": "^2.0.1",
67
69
  "doctoc": "^2.2.0",
70
+ "dom-helpers": "^6.0.1",
68
71
  "eslint-config-4catalyzer": "^1.4.1",
69
72
  "eslint-config-4catalyzer-typescript": "^3.2.1",
70
73
  "eslint-config-prettier": "^8.5.0",
@@ -84,11 +87,12 @@
84
87
  "mocha": "^9.2.2",
85
88
  "p-defer": "^3.0.0",
86
89
  "prettier": "^2.6.2",
90
+ "process": "^0.11.10",
87
91
  "puppeteer": "^13.7.0",
88
- "redux": "^4.1.2",
89
92
  "rimraf": "^3.0.2",
90
93
  "sinon": "^11.1.2",
91
94
  "sinon-chai": "^3.7.0",
95
+ "util": "^0.12.5",
92
96
  "webpack": "^5.72.1"
93
97
  }
94
98
  }
@@ -0,0 +1,6 @@
1
+ {
2
+ "private": true,
3
+ "name": "navigation-stack/redux",
4
+ "main": "./../lib/cjs/redux/index.js",
5
+ "module": "./../lib/esm/redux/index.js"
6
+ }
@@ -0,0 +1,6 @@
1
+ {
2
+ "private": true,
3
+ "name": "navigation-stack/scroll-position",
4
+ "main": "./../lib/cjs/scroll-position/index.js",
5
+ "module": "./../lib/esm/scroll-position/index.js"
6
+ }
@@ -0,0 +1,84 @@
1
+ import { applyMiddleware, createStore } from 'redux';
2
+
3
+ import Actions from './redux/Actions';
4
+ import createMiddlewares from './redux/createMiddlewares';
5
+ import locationReducer from './redux/locationReducer';
6
+ import ScrollPositionRestoration from './scroll-position/ScrollPositionRestoration';
7
+
8
+ export default class NavigationStack {
9
+ constructor(session, options) {
10
+ this._session = session;
11
+
12
+ // Create a Redux store.
13
+ this._store = createStore(
14
+ locationReducer,
15
+ applyMiddleware(...createMiddlewares(session, options)),
16
+ );
17
+
18
+ // Create `ScrollPositionRestoration`.
19
+ if (options && options.maintainScrollPosition) {
20
+ this._scrollPositionRestoration = new ScrollPositionRestoration(session);
21
+ }
22
+ }
23
+
24
+ addScrollableContainer(scrollableContainerKey, scrollableContainer) {
25
+ if (!this._scrollPositionRestoration) {
26
+ throw new Error('`maintainScrollPosition: true` option not passed');
27
+ }
28
+ return this._scrollPositionRestoration.addScrollableContainer(
29
+ scrollableContainerKey,
30
+ scrollableContainer,
31
+ );
32
+ }
33
+
34
+ subscribe(listener) {
35
+ // Subscribe to any potential Redux state changes.
36
+ return this._store.subscribe(() => {
37
+ // Initially, calls the listener when setting the initial location.
38
+ // After that, calls it on any location change.
39
+ const location = this.current();
40
+ if (!this._latestLocation || location !== this._latestLocation) {
41
+ this._latestLocation = location;
42
+ listener(location);
43
+ }
44
+ });
45
+ }
46
+
47
+ init(initialLocation) {
48
+ if (this._latestLocation) {
49
+ throw new Error('Already initialized');
50
+ }
51
+
52
+ this._store.dispatch(Actions.init(initialLocation));
53
+ this._latestLocation = this.current();
54
+ }
55
+
56
+ current() {
57
+ return this._store.getState();
58
+ }
59
+
60
+ push(location) {
61
+ this._store.dispatch(Actions.push(location));
62
+ }
63
+
64
+ replace(location) {
65
+ this._store.dispatch(Actions.replace(location));
66
+ }
67
+
68
+ shift(delta) {
69
+ this._store.dispatch(Actions.shift(delta));
70
+ }
71
+
72
+ stop() {
73
+ if (this._scrollPositionRestoration) {
74
+ this._scrollPositionRestoration.stop();
75
+ }
76
+ this._store.dispatch(Actions.stop());
77
+ }
78
+
79
+ locationRendered() {
80
+ if (this._scrollPositionRestoration) {
81
+ this._scrollPositionRestoration.locationRendered(this.current());
82
+ }
83
+ }
84
+ }
@@ -0,0 +1,69 @@
1
+ export default class DataStorage {
2
+ constructor(session, { namespace }) {
3
+ if (!session.key) {
4
+ throw new Error('`DataStorage` requires a `session.key`');
5
+ }
6
+ this._sessionKey = session.key;
7
+ this._dataStorage = session.environment.dataStorage;
8
+ this._namespace = namespace;
9
+ }
10
+
11
+ get(key) {
12
+ const storageKey = this._getStorageKey(key);
13
+
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
+
33
+ set(key, value) {
34
+ const storageKey = this._getStorageKey(key);
35
+
36
+ if (value === undefined) {
37
+ try {
38
+ this._dataStorage.remove(storageKey);
39
+ } catch (error) {
40
+ // No need to handle errors here.
41
+ // eslint-disable-next-line no-console
42
+ console.error('[navigation-stack] Could not delete data from storage');
43
+ }
44
+
45
+ return;
46
+ }
47
+
48
+ // Unlike with read, we want to fail on invalid values here, since the
49
+ // value here is provided by the caller of this method.
50
+ const valueString = JSON.stringify(value);
51
+
52
+ try {
53
+ this._dataStorage.set(storageKey, valueString);
54
+ } catch (error) {
55
+ // No need to handle errors here either. If it didn't work, it didn't
56
+ // 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');
59
+ }
60
+ }
61
+
62
+ // It could also implement a method that would clean up any data written so far,
63
+ // but so far it doesn't seem to be required by any real-world use case.
64
+ // cleanUp() {}
65
+
66
+ _getStorageKey(key) {
67
+ return `${this._sessionKey}|${this.namespace}|${key}`;
68
+ }
69
+ }
@@ -0,0 +1,23 @@
1
+ import DataStorage from './DataStorage';
2
+ import getLocationUrl from '../getLocationUrl';
3
+
4
+ export default class LocationDataStorage {
5
+ constructor(session, { namespace }) {
6
+ this._storage = new DataStorage(session, { namespace });
7
+
8
+ this._getFallbackLocationKey = getLocationUrl;
9
+ }
10
+
11
+ get(location, key) {
12
+ return this._storage.get(this._getKey(location, key));
13
+ }
14
+
15
+ set(location, key, value) {
16
+ this._storage.set(this._getKey(location, key), value);
17
+ }
18
+
19
+ _getKey(location, key) {
20
+ const locationKey = location.key || this._getFallbackLocationKey(location);
21
+ return `${locationKey}|${key}`;
22
+ }
23
+ }
@@ -0,0 +1,2 @@
1
+ export DataStorage from './DataStorage';
2
+ export LocationDataStorage from './LocationDataStorage';
@@ -0,0 +1,9 @@
1
+ import InMemoryDataStorage from './data-storage/InMemoryDataStorage';
2
+ import InMemoryScrollPosition from './scroll-position/InMemoryScrollPosition';
3
+
4
+ export default class InMemoryEnvironment {
5
+ constructor() {
6
+ this.dataStorage = new InMemoryDataStorage();
7
+ this.scrollPosition = new InMemoryScrollPosition();
8
+ }
9
+ }
@@ -0,0 +1,9 @@
1
+ import WebBrowserDataStorage from './data-storage/WebBrowserDataStorage';
2
+ import WebBrowserScrollPosition from './scroll-position/WebBrowserScrollPosition';
3
+
4
+ export default class WebBrowserEnvironment {
5
+ constructor() {
6
+ this.dataStorage = new WebBrowserDataStorage();
7
+ this.scrollPosition = new WebBrowserScrollPosition();
8
+ }
9
+ }
@@ -0,0 +1,23 @@
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
+
14
+ remove(key) {
15
+ if (key in this._state) {
16
+ delete this._state[key];
17
+ }
18
+ }
19
+
20
+ set(key, value) {
21
+ this._state[key] = value;
22
+ }
23
+ }
@@ -0,0 +1,17 @@
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
+
8
+ remove(key) {
9
+ // `sessionStorage` persists across page reloads, and so does web browser navigation history.
10
+ window.sessionStorage.removeItem(key);
11
+ }
12
+
13
+ set(key, value) {
14
+ // `sessionStorage` persists across page reloads, and so does web browser navigation history.
15
+ window.sessionStorage.setItem(key, value);
16
+ }
17
+ }
@@ -0,0 +1,45 @@
1
+ export default class InMemoryScrollPosition {
2
+ constructor() {
3
+ this.init();
4
+ }
5
+
6
+ getPageScrollPosition() {
7
+ return this._pageScrollPosition || [0, 0];
8
+ }
9
+
10
+ setPageScrollPosition(scrollPosition) {
11
+ this._pageScrollPosition = scrollPosition;
12
+ }
13
+
14
+ // eslint-disable-next-line no-unused-vars
15
+ setPageScrollPositionAtAnchor(anchor) {
16
+ this.setPageScrollPosition([0, 0]);
17
+ }
18
+
19
+ getScrollableContainerScrollPosition(key) {
20
+ return this._scrollableContainerScrollPositions[key] || [0, 0];
21
+ }
22
+
23
+ setScrollableContainerScrollPosition(key, scrollPosition) {
24
+ this._scrollableContainerScrollPositions[key] = scrollPosition;
25
+ }
26
+
27
+ // eslint-disable-next-line no-unused-vars
28
+ addPageScrollListener(listener) {
29
+ return () => {};
30
+ }
31
+
32
+ // eslint-disable-next-line no-unused-vars
33
+ addScrollableContainerScrollListener(scrollableContainerElement, listener) {
34
+ return () => {};
35
+ }
36
+
37
+ enableAutomaticScrollRestoration() {}
38
+
39
+ disableAutomaticScrollRestoration() {}
40
+
41
+ init() {
42
+ this._pageScrollPosition = undefined;
43
+ this._scrollableContainerScrollPositions = {};
44
+ }
45
+ }
@@ -0,0 +1,72 @@
1
+ export default class WebBrowserScrollPosition {
2
+ getPageScrollPosition() {
3
+ return [window.pageXOffset, window.pageYOffset];
4
+ }
5
+
6
+ setPageScrollPosition(scrollPosition) {
7
+ const [scrollX, scrollY] = scrollPosition;
8
+ window.scrollTo(scrollX, scrollY);
9
+ }
10
+
11
+ setPageScrollPositionAtAnchor(anchor) {
12
+ const anchorElement =
13
+ document.getElementById(anchor) || document.getElementsByName(anchor)[0];
14
+ if (anchorElement) {
15
+ // By default it scrolls the element into view
16
+ // so that it's visible at the top of the window.
17
+ anchorElement.scrollIntoView();
18
+ } else {
19
+ this.setPageScrollPosition([0, 0]);
20
+ }
21
+ }
22
+
23
+ getScrollableContainerScrollPosition(scrollableContainerElement) {
24
+ return [
25
+ scrollableContainerElement.scrollLeft,
26
+ scrollableContainerElement.scrollTop,
27
+ ];
28
+ }
29
+
30
+ setScrollableContainerScrollPosition(
31
+ scrollableContainerElement,
32
+ scrollPosition,
33
+ ) {
34
+ const [scrollX, scrollY] = scrollPosition;
35
+ scrollableContainerElement.scrollLeft = scrollX;
36
+ scrollableContainerElement.scrollTop = scrollY;
37
+ }
38
+
39
+ addPageScrollListener(listener) {
40
+ // eslint-disable-next-line no-unused-vars
41
+ const scrollListener = (event) => {
42
+ listener();
43
+ };
44
+
45
+ window.addEventListener('scroll', scrollListener);
46
+ return () => {
47
+ window.removeEventListener('scroll', scrollListener);
48
+ };
49
+ }
50
+
51
+ addScrollableContainerScrollListener(scrollableContainerElement, listener) {
52
+ // eslint-disable-next-line no-unused-vars
53
+ const scrollListener = (event) => {
54
+ listener();
55
+ };
56
+
57
+ scrollableContainerElement.addEventListener('scroll', scrollListener);
58
+ return () => {
59
+ scrollableContainerElement.removeEventListener('scroll', scrollListener);
60
+ };
61
+ }
62
+
63
+ enableAutomaticScrollRestoration() {
64
+ window.history.scrollRestoration = 'auto';
65
+ }
66
+
67
+ disableAutomaticScrollRestoration() {
68
+ window.history.scrollRestoration = 'manual';
69
+ }
70
+
71
+ init() {}
72
+ }
@@ -0,0 +1,7 @@
1
+ // Converts `LocationInternal` object to a publicly-visible `Location` object.
2
+ // It hides non-essential properties of location such as `operation`, `index`, `delta`.
3
+ export default function getLocationFromInternalLocation(internalLocation) {
4
+ // eslint-disable-next-line no-unused-vars
5
+ const { operation, index, delta, ...location } = internalLocation;
6
+ return location;
7
+ }
package/src/index.js CHANGED
@@ -1,12 +1,14 @@
1
- export Actions from './Actions';
2
- export ActionTypes from './ActionTypes';
3
1
  export { addBasePath, removeBasePath } from './basePath';
4
2
  export addNavigationBlocker from './addNavigationBlocker';
5
3
  export getLocationUrl from './getLocationUrl';
6
4
  export parseLocationUrl from './parseLocationUrl';
7
- export createMiddlewares from './createMiddlewares';
8
- export locationReducer from './locationReducer';
9
- export LocationDataStorage from './LocationDataStorage';
10
- export BrowserSession from './session/BrowserSession';
11
- export MemorySession from './session/MemorySession';
12
- export ServerSession from './session/ServerSession';
5
+ export parseInputLocation from './parseInputLocation';
6
+ export NavigationStack from './NavigationStack';
7
+ export DataStorage from './data-storage/DataStorage';
8
+ export LocationDataStorage from './data-storage/LocationDataStorage';
9
+ export Session from './session/Session';
10
+ export InMemorySession from './session/InMemorySession';
11
+ export WebBrowserSession from './session/WebBrowserSession';
12
+ export ServerSideRenderSession from './session/ServerSideRenderSession';
13
+ export ServerSideNavigationError from './session/navigation/error/ServerSideNavigationError';
14
+ export NavigationOutOfBoundsError from './session/navigation/error/NavigationOutOfBoundsError';
@@ -23,15 +23,15 @@ function removeNavigationBlockerFromTheList(blocker, session) {
23
23
 
24
24
  export function removeAllNavigationBlockers(session) {
25
25
  if (
26
- getNavigationBlockers(session).some((blocker) => blocker.beforeDestroy)
26
+ getNavigationBlockers(session).some((blocker) => blocker.beforeTermination)
27
27
  ) {
28
- if (!session._removeBeforeDestroyListener) {
28
+ if (!session._removeTerminationBlocker) {
29
29
  throw new Error(
30
- '`_removeBeforeDestroyListener` property not found in the `session`',
30
+ '`_removeTerminationBlocker` property not found in the `session`',
31
31
  );
32
32
  }
33
- session._removeBeforeDestroyListener();
34
- session._removeBeforeDestroyListener = undefined;
33
+ session._removeTerminationBlocker();
34
+ session._removeTerminationBlocker = undefined;
35
35
  }
36
36
  session._navigationBlockersList = [];
37
37
  }
@@ -99,34 +99,34 @@ export function runNavigationBlockers(navigationBlockers, toLocation) {
99
99
  }
100
100
 
101
101
  /* istanbul ignore next: not testable with Karma */
102
- function onBeforeDestroy(session) {
102
+ function terminationBlocker(session) {
103
103
  const result = runNavigationBlockers(getNavigationBlockers(session), null);
104
104
 
105
- // If no blocker returned anything, don't prevent the "unload" event.
105
+ // If no blocker returned anything, so don't prevent the navigation.
106
106
  if (!result) {
107
107
  return undefined;
108
108
  }
109
109
 
110
110
  // Web browsers don't allow displaying a custom modal in "beforeunload" phase.
111
111
  // They only allow displaying a standard one, with the default text.
112
- // Hence, "asynchronous" blockers should be ignored.
112
+ // Hence, "asynchronous" blockers should be ignored because web browsers won't wait for those to finish anyway.
113
113
  // https://developer.mozilla.org/en-US/docs/Web/API/Window/beforeunload_event
114
114
  if (isPromise(result)) {
115
115
  return undefined;
116
116
  }
117
117
 
118
- // Prevent the "unload" event.
118
+ // Block the navigation.
119
119
  return true;
120
120
  }
121
121
 
122
122
  export function addNavigationBlocker(session, blocker) {
123
- // All navigation blockers also run on `beforeDestroy` event.
123
+ // All navigation blockers also run on `beforeTermination` event.
124
124
  // If required, this could be a parameter of this function.
125
125
  // The rationale could be that adding a `beforeunload` listener
126
126
  // disables web page caching in some browsers like Firefox.
127
- const beforeDestroy = true;
127
+ const beforeTermination = true;
128
128
 
129
- // If it's the first "beforeDestroy" blocker, add the global `onBeforeDestroy` listener.
129
+ // If it's the first "beforeTermination" blocker, add a `terminationBlocker`.
130
130
  //
131
131
  // Sidenote: Add the "beforeunload" event listener only as needed, as its presence
132
132
  // prevents the page from being added to the page navigation cache:
@@ -137,41 +137,42 @@ export function addNavigationBlocker(session, blocker) {
137
137
  //
138
138
  // https://developer.mozilla.org/en-US/docs/Web/API/Window/beforeunload_event
139
139
  if (
140
- beforeDestroy &&
140
+ beforeTermination &&
141
141
  !getNavigationBlockers(session).some(
142
- (navigationBlocker) => navigationBlocker.beforeDestroy,
142
+ (navigationBlocker) => navigationBlocker.beforeTermination,
143
143
  )
144
144
  ) {
145
- if (session._removeBeforeDestroyListener) {
145
+ if (session._removeTerminationBlocker) {
146
146
  throw new Error(
147
- 'Unexpected `_removeBeforeDestroyListener` property found in the `session`',
147
+ 'Unexpected `_removeTerminationBlocker` property found in the `session`',
148
148
  );
149
149
  }
150
- session._removeBeforeDestroyListener = session.addBeforeDestroyListener(
151
- () => onBeforeDestroy(session),
152
- );
150
+ session._removeTerminationBlocker =
151
+ session.lifecycle.addTerminationBlocker(() => {
152
+ return terminationBlocker(session);
153
+ });
153
154
  }
154
155
 
155
- const newNavigationBlocker = { blocker, beforeDestroy };
156
+ const newNavigationBlocker = { blocker, beforeTermination };
156
157
  addNavigationBlockerToTheList(newNavigationBlocker, session);
157
158
 
158
159
  return () => {
159
160
  removeNavigationBlockerFromTheList(newNavigationBlocker, session);
160
161
 
161
- // If it was the last "beforeDestroy" blocker, remove the global `onBeforeDestroy` listener.
162
+ // If it was the last "beforeTermination" blocker, remove navigation blocker.
162
163
  if (
163
- beforeDestroy &&
164
+ beforeTermination &&
164
165
  !getNavigationBlockers(session).some(
165
- (navigationBlocker) => navigationBlocker.beforeDestroy,
166
+ (navigationBlocker) => navigationBlocker.beforeTermination,
166
167
  )
167
168
  ) {
168
- if (!session._removeBeforeDestroyListener) {
169
+ if (!session._removeTerminationBlocker) {
169
170
  throw new Error(
170
- '`_removeBeforeDestroyListener` property not found in the `session`',
171
+ '`_removeTerminationBlocker` property not found in the `session`',
171
172
  );
172
173
  }
173
- session._removeBeforeDestroyListener();
174
- session._removeBeforeDestroyListener = undefined;
174
+ session._removeTerminationBlocker();
175
+ session._removeTerminationBlocker = undefined;
175
176
  }
176
177
  };
177
178
  }
@@ -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,15 @@ 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 = {
18
+ ...location,
19
+ query: {
20
+ ...location.query,
21
+ [key]: String(location.query[key]),
22
+ },
23
+ };
24
+ }
17
25
  }
18
26
  }
19
27
 
@@ -34,12 +42,19 @@ export default function normalizeInputLocation(location) {
34
42
  };
35
43
  }
36
44
 
37
- // Set default values on `search` and `hash`
38
- // if those properties are not present.
39
- return {
40
- ...location,
45
+ // Set default values for properties that're not present.
46
+ // Ignore unknown properties.
47
+ const { state } = location;
48
+ location = {
49
+ pathname: location.pathname,
41
50
  query: location.query || {},
42
51
  search: location.search || '',
43
52
  hash: location.hash || '',
44
53
  };
54
+ if (state) {
55
+ location.state = state;
56
+ }
57
+
58
+ // Return `location`.
59
+ return location;
45
60
  }
@@ -5,5 +5,5 @@ export default {
5
5
  NAVIGATE: '@@navigation-stack/NAVIGATE',
6
6
  SHIFT: '@@navigation-stack/SHIFT',
7
7
  UPDATE: '@@navigation-stack/UPDATE',
8
- DISPOSE: '@@navigation-stack/DISPOSE',
8
+ STOP: '@@navigation-stack/STOP',
9
9
  };
@@ -0,0 +1,3 @@
1
+ export default {
2
+ INTERNAL_LOCATION_UPDATE: '@@navigation-stack/INTERNAL_LOCATION_UPDATE',
3
+ };