navigation-stack 0.1.3 → 0.3.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 (77) hide show
  1. package/.github/workflows/main.yml +39 -39
  2. package/README.md +128 -27
  3. package/lib/cjs/{LocationStateStorage.js → LocationDataStorage.js} +5 -5
  4. package/lib/cjs/addBeforeLocationChangeListener.js +7 -0
  5. package/lib/cjs/beforeLocationChangeListeners.js +51 -0
  6. package/lib/cjs/createMiddlewares.js +21 -17
  7. package/lib/cjs/index.js +9 -9
  8. package/lib/cjs/middleware/createBasePathMiddleware.js +2 -2
  9. package/lib/cjs/middleware/createBeforeLocationChangeListenerMiddleware.js +39 -0
  10. package/lib/cjs/middleware/{createEnvironmentMiddleware.js → createLocationMiddleware.js} +12 -14
  11. package/lib/cjs/middleware/createNavigationBlockerMiddleware.js +62 -29
  12. package/lib/cjs/middleware/createTransformLocationMiddleware.js +2 -2
  13. package/lib/cjs/navigationBlockers.js +55 -47
  14. package/lib/cjs/normalizeInputLocation.js +1 -0
  15. package/lib/cjs/parseLocationUrl.js +2 -0
  16. package/lib/cjs/parseQueryFromSearch.js +1 -1
  17. package/lib/cjs/session/BrowserSession.js +235 -0
  18. package/lib/cjs/session/MemorySession.js +223 -0
  19. package/lib/cjs/{environment/ServerEnvironment.js → session/ServerSession.js} +28 -16
  20. package/lib/esm/{LocationStateStorage.js → LocationDataStorage.js} +4 -4
  21. package/lib/esm/addBeforeLocationChangeListener.js +2 -0
  22. package/lib/esm/beforeLocationChangeListeners.js +44 -0
  23. package/lib/esm/createMiddlewares.js +21 -17
  24. package/lib/esm/index.js +4 -4
  25. package/lib/esm/middleware/createBasePathMiddleware.js +2 -2
  26. package/lib/esm/middleware/createBeforeLocationChangeListenerMiddleware.js +34 -0
  27. package/lib/esm/middleware/{createEnvironmentMiddleware.js → createLocationMiddleware.js} +11 -13
  28. package/lib/esm/middleware/createNavigationBlockerMiddleware.js +62 -29
  29. package/lib/esm/middleware/createTransformLocationMiddleware.js +2 -2
  30. package/lib/esm/navigationBlockers.js +55 -47
  31. package/lib/esm/normalizeInputLocation.js +1 -0
  32. package/lib/esm/parseLocationUrl.js +2 -0
  33. package/lib/esm/parseQueryFromSearch.js +1 -1
  34. package/lib/esm/session/BrowserSession.js +229 -0
  35. package/lib/esm/session/MemorySession.js +217 -0
  36. package/lib/esm/{environment/ServerEnvironment.js → session/ServerSession.js} +27 -15
  37. package/lib/index.d.ts +66 -65
  38. package/package.json +2 -7
  39. package/src/{LocationStateStorage.js → LocationDataStorage.js} +4 -4
  40. package/src/addBeforeLocationChangeListener.js +2 -0
  41. package/src/beforeLocationChangeListeners.js +54 -0
  42. package/src/createMiddlewares.js +21 -17
  43. package/src/index.js +4 -4
  44. package/src/middleware/createBasePathMiddleware.js +2 -2
  45. package/src/middleware/createBeforeLocationChangeListenerMiddleware.js +40 -0
  46. package/src/middleware/{createEnvironmentMiddleware.js → createLocationMiddleware.js} +12 -14
  47. package/src/middleware/createNavigationBlockerMiddleware.js +68 -28
  48. package/src/middleware/createTransformLocationMiddleware.js +2 -2
  49. package/src/navigationBlockers.js +68 -49
  50. package/src/normalizeInputLocation.js +1 -0
  51. package/src/parseLocationUrl.js +2 -0
  52. package/src/parseQueryFromSearch.js +1 -1
  53. package/src/session/BrowserSession.js +235 -0
  54. package/src/session/MemorySession.js +219 -0
  55. package/src/{environment/ServerEnvironment.js → session/ServerSession.js} +28 -15
  56. package/test/{LocationStateStorage.test.js → LocationDataStorage.test.js} +6 -6
  57. package/test/createMiddlewares.test.js +2 -2
  58. package/test/helpers.js +1 -1
  59. package/test/index.test.js +3 -3
  60. package/test/middleware/createBasePathMiddleware.test.js +7 -7
  61. package/test/middleware/createBeforeLocationChangeListenerMiddleware.test.js +141 -0
  62. package/test/middleware/createNavigationBlockerMiddleware.test.js +100 -101
  63. package/test/middleware/createTransformLocationMiddleware.test.js +1 -1
  64. package/test/normalizeInputLocation.test.js +3 -0
  65. package/test/parseLocationUrl.test.js +2 -0
  66. package/test/{environment/BrowserEnvironment.test.js → session/BrowserSession.test.js} +38 -21
  67. package/test/session/MemorySession.test.js +244 -0
  68. package/test/session/ServerSession.test.js +23 -0
  69. package/types/index.d.ts +66 -65
  70. package/lib/cjs/environment/BrowserEnvironment.js +0 -111
  71. package/lib/cjs/environment/MemoryEnvironment.js +0 -150
  72. package/lib/esm/environment/BrowserEnvironment.js +0 -104
  73. package/lib/esm/environment/MemoryEnvironment.js +0 -143
  74. package/src/environment/BrowserEnvironment.js +0 -109
  75. package/src/environment/MemoryEnvironment.js +0 -151
  76. package/test/environment/MemoryEnvironment.test.js +0 -218
  77. package/test/environment/ServerEnvironment.test.js +0 -23
@@ -0,0 +1,217 @@
1
+ /* eslint-disable max-classes-per-file */
2
+
3
+ import normalizeInputLocation from '../normalizeInputLocation';
4
+
5
+ // eslint-disable-next-line no-underscore-dangle
6
+ function _loadState(load, isValidLoadedData) {
7
+ try {
8
+ const data = JSON.parse(load());
9
+
10
+ // Check that the stack and index at least seem reasonable before using
11
+ // them as state. This isn't foolproof, but it might prevent mistakes.
12
+ // Also perform a basic validation of `state`.
13
+ if (isValidLoadedData(data)) {
14
+ return data;
15
+ }
16
+ } catch (error) {} // eslint-disable-line no-empty
17
+
18
+ return null;
19
+ }
20
+
21
+ // eslint-disable-next-line no-underscore-dangle
22
+ function _saveState(save, data) {
23
+ try {
24
+ save(JSON.stringify(data));
25
+ } catch (error) {} // eslint-disable-line no-empty
26
+ }
27
+ class MemoryNavigation {
28
+ constructor(initialLocation, {
29
+ save,
30
+ load
31
+ } = {}) {
32
+ this._save = save;
33
+ this._keyPrefix = Date.now().toString(36);
34
+ this._keyIndex = 0;
35
+ this._subscriptionListener = null;
36
+ const initialState = load ? _loadState(load, this._isValidLoadedData) : null;
37
+ if (initialState) {
38
+ this._stack = initialState.stack;
39
+ this._index = initialState.index;
40
+ } else {
41
+ this._stack = [Object.assign({}, normalizeInputLocation(initialLocation), {
42
+ key: this._getNextKey()
43
+ })];
44
+ this._index = 0;
45
+ }
46
+ }
47
+ _isValidLoadedData({
48
+ stack,
49
+ index
50
+ }) {
51
+ // Check that the `stack` and `index` at least seem reasonable before using them.
52
+ // This isn't foolproof, but it might prevent mistakes.
53
+ return Array.isArray(stack) && typeof index === 'number' && stack[index];
54
+ }
55
+ init() {
56
+ return this._createLocationObject({
57
+ action: 'INIT',
58
+ delta: 0
59
+ });
60
+ }
61
+ subscribe(listener) {
62
+ this._subscriptionListener = listener;
63
+ return () => {
64
+ this._subscriptionListener = null;
65
+ };
66
+ }
67
+ navigate(location) {
68
+ const {
69
+ action,
70
+ pathname,
71
+ search,
72
+ query,
73
+ hash,
74
+ state
75
+ } = location;
76
+ if (action !== 'PUSH' && action !== 'REPLACE') {
77
+ throw Error(`Unrecognized browser session action: ${action}`);
78
+ }
79
+ const delta = action === 'PUSH' ? 1 : 0;
80
+ this._index += delta;
81
+ const key = this._getNextKey();
82
+ this._stack[this._index] = {
83
+ pathname,
84
+ search,
85
+ query,
86
+ hash,
87
+ state,
88
+ key
89
+ };
90
+ if (action === 'PUSH') {
91
+ this._stack.length = this._index + 1;
92
+ }
93
+ if (this._save) {
94
+ _saveState(this._save, {
95
+ stack: this._stack,
96
+ index: this._index
97
+ });
98
+ }
99
+ return Object.assign({}, location, {
100
+ key,
101
+ index: this._index,
102
+ delta
103
+ });
104
+ }
105
+ shift(delta) {
106
+ const prevIndex = this._index;
107
+ this._index = Math.min(Math.max(this._index + delta, 0), this._stack.length - 1);
108
+ if (this._index === prevIndex) {
109
+ return;
110
+ }
111
+ if (this._save) {
112
+ _saveState(this._save, {
113
+ stack: this._stack,
114
+ index: this._index
115
+ });
116
+ }
117
+ if (this._subscriptionListener) {
118
+ this._subscriptionListener(this._createLocationObject({
119
+ action: 'SHIFT',
120
+ delta: this._index - prevIndex
121
+ }));
122
+ }
123
+ }
124
+ _getNextKey() {
125
+ const key = `${this._keyPrefix}.${this._keyIndex.toString(36)}`;
126
+ this._keyIndex++;
127
+ return key;
128
+ }
129
+ _createLocationObject({
130
+ action,
131
+ delta
132
+ }) {
133
+ return Object.assign({}, this._stack[this._index], {
134
+ action,
135
+ index: this._index,
136
+ delta
137
+ });
138
+ }
139
+ }
140
+ class MemoryDataStorage {
141
+ constructor({
142
+ load,
143
+ save
144
+ } = {}) {
145
+ this._save = save;
146
+ const initialState = load ? _loadState(load, this._isValidLoadedData) : null;
147
+ if (initialState) {
148
+ this._state = initialState.state;
149
+ } else {
150
+ this._state = {};
151
+ }
152
+ }
153
+
154
+ // Returns either a `string` value or `null` if the key doesn't exist.
155
+ get(key) {
156
+ if (key in this._state) {
157
+ return this._state[key];
158
+ }
159
+ return null;
160
+ }
161
+ remove(key) {
162
+ if (key in this._state) {
163
+ delete this._state[key];
164
+ }
165
+ if (this._save) {
166
+ _saveState(this._save, {
167
+ state: this._state
168
+ });
169
+ }
170
+ }
171
+ set(key, value) {
172
+ this._state[key] = value;
173
+ if (this._save) {
174
+ _saveState(this._save, {
175
+ state: this._state
176
+ });
177
+ }
178
+ }
179
+ _isValidLoadedData({
180
+ state
181
+ }) {
182
+ // Perform a basic validation of `state`.
183
+ return typeof state === 'object' && state !== null;
184
+ }
185
+ }
186
+ function createNestedStateSaveLoadFunctions({
187
+ save,
188
+ load
189
+ }, key) {
190
+ return {
191
+ save: save ? data => save(key, data) : undefined,
192
+ load: load ? () => load(key) : undefined
193
+ };
194
+ }
195
+ export default class MemorySession {
196
+ constructor(initialLocation, {
197
+ save,
198
+ load
199
+ } = {}) {
200
+ this.navigation = new MemoryNavigation(initialLocation, createNestedStateSaveLoadFunctions({
201
+ save,
202
+ load
203
+ }, 'navigation'));
204
+ this.dataStorage = new MemoryDataStorage(createNestedStateSaveLoadFunctions({
205
+ save,
206
+ load
207
+ }, 'dataStorage'));
208
+ }
209
+
210
+ // "Before destroy" listeners are currently ignored.
211
+ // If required, one could implement a `_destroy()` method
212
+ // and there check that the listeners actually do get called.
213
+ // eslint-disable-next-line no-unused-vars
214
+ addBeforeDestroyListener(listener) {
215
+ return () => {};
216
+ }
217
+ }
@@ -1,46 +1,58 @@
1
+ /* eslint-disable max-classes-per-file */
2
+
1
3
  import normalizeInputLocation from '../normalizeInputLocation';
2
4
  function noop() {}
3
5
  function serverSideNavigationNotPossible() {
4
6
  throw new Error('Server-side navigation is not possible');
5
7
  }
6
- export default class ServerEnvironment {
8
+ class ServerNavigation {
7
9
  constructor(initialLocation) {
8
10
  this._location = normalizeInputLocation(initialLocation);
9
11
  }
10
12
  init() {
11
13
  return Object.assign({
12
- action: 'POP'
13
- }, this._location);
14
+ action: 'INIT'
15
+ }, this._location, {
16
+ index: 0,
17
+ key: '0'
18
+ });
14
19
  }
15
20
  subscribe() {
16
- // Server environment emits no location events.
21
+ // Server-side environment emits no location subscription events.
17
22
  return noop;
18
23
  }
19
24
 
20
- // Navigation methods are not implemented, because `ServerPEnvironment` instances
25
+ // Navigation methods are not implemented, because `ServerSession` instances
21
26
  // cannot navigate.
22
27
  navigate() {
23
28
  serverSideNavigationNotPossible();
24
29
  }
25
30
 
26
- // Navigation methods are not implemented, because `ServerEnvironment` instances
31
+ // Navigation methods are not implemented, because `ServerSession` instances
27
32
  // cannot navigate.
28
33
  shift() {
29
34
  serverSideNavigationNotPossible();
30
35
  }
36
+ }
37
+ class ServerDataStorage {
38
+ // It doesn't seem to make any sense to store anything on server side.
39
+ // Hence, state management methods are "no op" stubs.
40
+ get() {
41
+ return null;
42
+ }
43
+ remove() {}
44
+ set() {}
45
+ }
46
+ export default class ServerSession {
47
+ constructor(initialLocation) {
48
+ this.navigation = new ServerNavigation(initialLocation);
49
+ this.dataStorage = new ServerDataStorage();
50
+ }
31
51
 
32
52
  // "Before destroy" listeners are currently ignored.
33
53
  // If required, one could implement a `_destroy()` method
34
54
  // and there check that the listeners actually do get called.
35
55
  addBeforeDestroyListener() {
36
- return () => {};
37
- }
38
-
39
- // It doesn't seem to make any sense to store anything on server side.
40
- // Hence, state management methods are "no op" stubs.
41
- getState() {
42
- return null;
56
+ return noop;
43
57
  }
44
- removeState() {}
45
- setState() {}
46
58
  }
package/lib/index.d.ts CHANGED
@@ -13,12 +13,9 @@ export type InputLocationQuery = Record<
13
13
 
14
14
  export interface Location<TState = any> {
15
15
  /**
16
- * 'PUSH' or 'REPLACE' if the location was reached via FarceActions.push or
17
- * FarceActions.replace respectively; 'POP' on the initial location, or if
18
- * the location was reached via the browser back or forward buttons or
19
- * via FarceActions.shift
16
+ * See the README on the `action` property of `location`.
20
17
  */
21
- action: 'PUSH' | 'REPLACE' | 'POP';
18
+ action: 'PUSH' | 'REPLACE' | 'SHIFT' | 'INIT';
22
19
  /**
23
20
  * the path name; as on window.location e.g. '/foo'
24
21
  */
@@ -36,9 +33,9 @@ export interface Location<TState = any> {
36
33
  */
37
34
  hash: string;
38
35
  /**
39
- * if present, a unique key identifying the current history entry
36
+ * a unique key identifying the current history entry
40
37
  */
41
- key?: string;
38
+ key: string;
42
39
  /**
43
40
  * the current index of the history entry, starting at 0 for the initial
44
41
  * entry; this increments on FarceActions.push but not on
@@ -46,12 +43,11 @@ export interface Location<TState = any> {
46
43
  */
47
44
  index: number;
48
45
  /**
49
- * the difference between the current index and the index of the previous
50
- * location
46
+ * the difference between the current index and the index of the previous location
51
47
  */
52
48
  delta: number;
53
49
  /**
54
- * additional location state that is not part of the URL
50
+ * any additional location state that the application might explicitly define and store
55
51
  */
56
52
  state: TState;
57
53
  }
@@ -70,11 +66,15 @@ export interface InputLocationObject {
70
66
  export interface LocationBase {
71
67
  pathname: Location['pathname'];
72
68
  search: Location['search'];
73
- query?: Query;
69
+ query: Query;
74
70
  hash: Location['hash'];
75
71
  state?: Location['state'];
76
72
  }
77
73
 
74
+ export interface NavigationLocation extends LocationBase {
75
+ action: 'PUSH' | 'REPLACE';
76
+ }
77
+
78
78
  /**
79
79
  * Location descriptor string:
80
80
  * store.dispatch(FarceActions.push('/foo?bar=baz#qux'));
@@ -109,13 +109,20 @@ export type NavigationBlockerResult =
109
109
  | Promise<NavigationBlockerSyncResult>;
110
110
 
111
111
  /**
112
- * The navigation listener function receives the `location` to which the user
113
- * is attempting to navigate.
112
+ * Navigation blocker function receives a `location` to which the application (or the user) is attempting to navigate.
114
113
  *
115
- * The `location` argument is `null` when the web browser tab is about to be closed.
114
+ * * The `location` argument is `null` when the web browser tab is about to be closed.
115
+ * * The `location` argument is of type `NavigationLocation` when a `.push()` or `.replace()` action is blocked.
116
+ * * The `location` argument is of type `Location` when blocking a navigation that was initiated outside of the application code.
117
+ * For example, when the user clicks "Back" or "Forward" button in a web browser.
116
118
  */
117
119
  export interface NavigationBlocker {
118
- (location: Location | LocationBase | null): NavigationBlockerResult;
120
+ (location: Location | NavigationLocation | null): NavigationBlockerResult;
121
+ }
122
+
123
+ // I dunno why did they use an `interface` here.
124
+ export interface BeforeLocationChangeListener {
125
+ (location: Location): void;
119
126
  }
120
127
 
121
128
  export function addBasePath<L extends InputLocation>(
@@ -131,15 +138,20 @@ export function getLocationUrl(location: InputLocationObject): string;
131
138
  export function parseLocationUrl(locationUrl: string): LocationBase;
132
139
 
133
140
  export function createMiddlewares(
134
- environment: Environment,
141
+ session: SessionBase,
135
142
  options?: CreateMiddlewaresOptions,
136
143
  ): Middleware[];
137
144
 
138
145
  export function addNavigationBlocker(
139
- environment: EnvironmentBase,
146
+ session: SessionBase,
140
147
  blocker: NavigationBlocker,
141
148
  ): () => void;
142
149
 
150
+ export function addBeforeLocationChangeListener(
151
+ session: SessionBase,
152
+ listener: BeforeLocationChangeListener,
153
+ ): () => void;
154
+
143
155
  export const ActionTypes: {
144
156
  INIT: '@@navigation-stack/INIT';
145
157
  PUSH: '@@navigation-stack/PUSH';
@@ -190,80 +202,73 @@ export const Actions: {
190
202
 
191
203
  type BeforeDestroyListener = () => boolean | undefined;
192
204
 
193
- export interface Environment {
205
+ interface SessionNavigation {
194
206
  init(): void;
195
207
 
196
208
  // Subscribes to changes in location,
197
209
  // excluding ones that happened as a result of calling `.navigate()`.
198
210
  subscribe(listener: (location: Location) => void): () => void;
199
211
 
200
- navigate(location: LocationBase): Location;
212
+ navigate(location: NavigationLocation): Location;
201
213
 
202
214
  shift(delta: number): void;
215
+ }
203
216
 
204
- addBeforeDestroyListener(listener: BeforeDestroyListener): void;
205
-
206
- getState(key: string): string | null;
207
- removeState(key: string): void;
208
- setState(key: string, value: string): void;
217
+ interface SessionDataStorage {
218
+ get(key: string): string | null;
219
+ remove(key: string): void;
220
+ set(key: string, value: string): void;
209
221
  }
210
222
 
211
- // This is just a copy-paste of the `Environment` interface above.
212
- declare abstract class EnvironmentBase implements Environment {
213
- init(): void;
223
+ export interface Session {
224
+ navigation: SessionNavigation;
225
+ dataStorage: SessionDataStorage;
226
+ addBeforeDestroyListener(listener: BeforeDestroyListener): void;
214
227
 
215
- // Subscribes to changes in location,
216
- // excluding ones that happened as a result of calling `.navigate()`.
217
- subscribe(listener: (location: Location) => void): () => void;
228
+ // These're internal variables that're manually set under the hood.
229
+ // _beforeLocationChangeListenersList?: Array<BeforeLocationChangeListener>;
230
+ // _navigationBlockersList?: Array<NavigationBlocker>;
231
+ // _removeBeforeDestroyListener?: () => void;
232
+ // _navigationBlockersEvaluationStatus?: { cancelled?: boolean };
233
+ }
218
234
 
219
- navigate(location: LocationBase): Location;
235
+ // This is just a copy-paste of the `session` interface above.
236
+ declare abstract class SessionBase implements Session {
237
+ navigation: SessionNavigation;
220
238
 
221
- shift(delta: number): void;
239
+ dataStorage: SessionDataStorage;
222
240
 
223
241
  addBeforeDestroyListener(listener: BeforeDestroyListener): void;
224
242
 
225
- getState(key: string): string | null;
226
- removeState(key: string): void;
227
- setState(key: string, value: string): void;
243
+ // These're internal variables that're manually set under the hood.
244
+ // _beforeLocationChangeListenersList?: Array<BeforeLocationChangeListener>;
245
+ // _navigationBlockersList?: Array<NavigationBlocker>;
246
+ // _removeBeforeDestroyListener?: () => void;
247
+ // _navigationBlockersEvaluationStatus?: { cancelled?: boolean };
228
248
  }
229
249
 
230
- export class BrowserEnvironment extends EnvironmentBase {}
250
+ export class BrowserSession extends SessionBase {}
231
251
 
232
- export interface MemoryEnvironmentOptions<MemoryEnvironmentState = any> {
233
- save?: (state: MemoryEnvironmentState) => void;
234
- load?: () => MemoryEnvironmentState | undefined | null;
252
+ export interface MemorySessionOptions {
253
+ save?: (data: string) => void;
254
+ load?: () => string | undefined | null;
235
255
  }
236
256
 
237
- export class ServerEnvironment extends EnvironmentBase {
257
+ export class ServerSession extends SessionBase {
238
258
  constructor(initialLocation: InputLocation);
239
259
  }
240
260
 
241
- export class MemoryEnvironment extends EnvironmentBase {
242
- constructor(
243
- initialLocation: InputLocation,
244
- options?: MemoryEnvironmentOptions,
245
- );
261
+ export class MemorySession extends SessionBase {
262
+ constructor(initialLocation: InputLocation, options?: MemorySessionOptions);
246
263
  }
247
264
 
248
- export interface QueryMiddlewareOptions {
249
- stringify(query: InputLocationQuery): string;
250
- parse(str: string): Query;
251
- }
252
-
253
- export function createQueryMiddleware(
254
- options: QueryMiddlewareOptions,
255
- ): Middleware;
256
-
257
- export const queryMiddleware: Middleware;
258
-
259
- export function createBasePathMiddleware(basePath?: string): Middleware;
260
-
261
265
  export const locationReducer: Reducer<Location, Action>;
262
266
 
263
- export class LocationStateStorage {
264
- constructor(environment: Environment, options?: { namespace?: string });
267
+ export class LocationDataStorage {
268
+ constructor(session: Session, options?: { namespace?: string });
265
269
 
266
270
  get(location: Location, key: string): any;
271
+
267
272
  set(location: Location, key: string, value: any): void;
268
273
  }
269
274
 
@@ -287,11 +292,7 @@ interface MiddlewareAPI<D extends Dispatch = Dispatch, S = any> {
287
292
  getState(): S;
288
293
  }
289
294
 
290
- interface Middleware<
291
- DispatchExt = {},
292
- S = any,
293
- D extends Dispatch = Dispatch,
294
- > {
295
+ interface Middleware<S = any, D extends Dispatch = Dispatch> {
295
296
  (api: MiddlewareAPI<D, S>): (next: Dispatch) => (action: any) => any;
296
297
  }
297
298
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "navigation-stack",
3
- "version": "0.1.3",
3
+ "version": "0.3.0",
4
4
  "description": "Handles navigation in a web browser",
5
5
  "keywords": [
6
6
  "history",
@@ -51,6 +51,7 @@
51
51
  "@4c/cli": "^3.0.1",
52
52
  "@4c/prettier-config": "^1.1.0",
53
53
  "@babel/core": "^7.17.10",
54
+ "@definitelytyped/dtslint": "0.0.182",
54
55
  "@typescript-eslint/eslint-plugin": "^5.23.0",
55
56
  "@typescript-eslint/parser": "^5.23.0",
56
57
  "babel-loader": "^8.2.5",
@@ -64,7 +65,6 @@
64
65
  "delay": "^4.4.1",
65
66
  "dirty-chai": "^2.0.1",
66
67
  "doctoc": "^2.2.0",
67
- "dtslint": "^4.2.1",
68
68
  "eslint-config-4catalyzer": "^1.4.1",
69
69
  "eslint-config-4catalyzer-typescript": "^3.2.1",
70
70
  "eslint-config-prettier": "^8.5.0",
@@ -90,10 +90,5 @@
90
90
  "sinon": "^11.1.2",
91
91
  "sinon-chai": "^3.7.0",
92
92
  "webpack": "^5.72.1"
93
- },
94
- "resolutions": {
95
- "dtslint/@definitelytyped/header-parser": "^0.0.41",
96
- "dtslint/@definitelytyped/typescript-versions": "^0.0.41",
97
- "dtslint/@definitelytyped/utils": "^0.0.41"
98
93
  }
99
94
  }
@@ -1,6 +1,6 @@
1
1
  import getLocationUrl from './getLocationUrl';
2
2
 
3
- export default class LocationStateStorage {
3
+ export default class LocationDataStorage {
4
4
  constructor(environment, { namespace } = {}) {
5
5
  this._environment = environment;
6
6
  this._getFallbackLocationKey = getLocationUrl;
@@ -11,7 +11,7 @@ export default class LocationStateStorage {
11
11
  const stateKey = this._getStateKey(location, key);
12
12
 
13
13
  try {
14
- const value = this._environment.getState(stateKey);
14
+ const value = this._environment.dataStorage.get(stateKey);
15
15
  // === null is probably sufficient.
16
16
  if (value === null) {
17
17
  return undefined;
@@ -31,7 +31,7 @@ export default class LocationStateStorage {
31
31
 
32
32
  if (value === undefined) {
33
33
  try {
34
- this._environment.removeState(stateKey);
34
+ this._environment.dataStorage.remove(stateKey);
35
35
  } catch (error) {
36
36
  // No need to handle errors here.
37
37
  }
@@ -44,7 +44,7 @@ export default class LocationStateStorage {
44
44
  const valueString = JSON.stringify(value);
45
45
 
46
46
  try {
47
- this._environment.setState(stateKey, valueString);
47
+ this._environment.dataStorage.set(stateKey, valueString);
48
48
  } catch (error) {
49
49
  // No need to handle errors here either. If it didn't work, it didn't
50
50
  // work. We make no guarantees about actually saving the value.
@@ -0,0 +1,2 @@
1
+ // eslint-disable-next-line no-restricted-exports
2
+ export { addBeforeLocationChangeListener as default } from './beforeLocationChangeListeners';
@@ -0,0 +1,54 @@
1
+ /* eslint-disable no-underscore-dangle */
2
+
3
+ export function getBeforeLocationChangeListeners(session) {
4
+ return session._beforeLocationChangeListenersList || [];
5
+ }
6
+
7
+ function addBeforeLocationChangeListenerToTheList(listener, session) {
8
+ if (!session._beforeLocationChangeListenersList) {
9
+ session._beforeLocationChangeListenersList = [];
10
+ }
11
+ session._beforeLocationChangeListenersList.push(listener);
12
+ }
13
+
14
+ function removeBeforeLocationChangeListenerFromTheList(listener, session) {
15
+ if (session._beforeLocationChangeListenersList) {
16
+ session._beforeLocationChangeListenersList =
17
+ session._beforeLocationChangeListenersList.filter((_) => _ !== listener);
18
+ }
19
+ }
20
+
21
+ export function removeAllBeforeLocationChangeListeners(session) {
22
+ session._beforeLocationChangeListenersList = [];
23
+ }
24
+
25
+ // Runs the `listener` while ignoring any errors that might be thrown by it.
26
+ function runBeforeLocationChangeListener(listener, location) {
27
+ try {
28
+ listener(location);
29
+ } catch (error) {
30
+ // eslint-disable-next-line no-console
31
+ console.warn(
32
+ `Ignoring before location change listener \`${listener.name}\` that failed with \`${error}\`.`,
33
+ );
34
+ // eslint-disable-next-line no-console
35
+ console.error(error);
36
+ }
37
+ }
38
+
39
+ // Runs all listeners in order.
40
+ export function runBeforeLocationChangeListeners(
41
+ navigationListeners,
42
+ toLocation,
43
+ ) {
44
+ for (const listener of navigationListeners) {
45
+ runBeforeLocationChangeListener(listener, toLocation);
46
+ }
47
+ }
48
+
49
+ export function addBeforeLocationChangeListener(session, listener) {
50
+ addBeforeLocationChangeListenerToTheList(listener, session);
51
+ return () => {
52
+ removeBeforeLocationChangeListenerFromTheList(listener, session);
53
+ };
54
+ }