navigation-stack 0.2.0 → 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.
package/README.md CHANGED
@@ -95,7 +95,7 @@ A `location` object has all the properties of a [standard web browser location](
95
95
  * `index: number` — The index of the location in the navigation history, starting with `0` for the initial location.
96
96
  * `action: string` — The type of navigation that led to the location.
97
97
  * `INIT` in case of the initial location before any navigation has taken place.
98
- * `POP` when the user performs a "Back" or "Forward" navigation, or after a `.shift()` navigation which is essentially a "back or forward navigation".
98
+ * `SHIFT` when the user performs a "Back" or "Forward" navigation, or after a `.shift()` navigation which is essentially a "back or forward navigation".
99
99
  * `PUSH` in case of a `.push()` navigation, i.e. "normal navigation via a hyperlink".
100
100
  * `REPLACE` in case of a `.replace()` navigation, i.e. "redirect".
101
101
  * `delta: number` — the difference between the `index` of the current location and the `index` of the previous location.
@@ -61,15 +61,21 @@ class BrowserNavigation {
61
61
  this._index = INITIAL_INDEX;
62
62
  }
63
63
  init() {
64
- return this._createEntryFromCurrentLocation();
64
+ return this._createEntryFromCurrentLocation('INIT');
65
65
  }
66
- _createEntryFromCurrentLocation() {
66
+ _createEntryFromCurrentLocation(action) {
67
67
  const {
68
68
  pathname,
69
69
  search,
70
70
  hash
71
71
  } = window.location;
72
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
+ }
73
79
  const {
74
80
  key,
75
81
  index,
@@ -80,7 +86,7 @@ class BrowserNavigation {
80
86
  state: undefined
81
87
  }) : this._restoreAdditionalPropertiesForCurrentLocation();
82
88
  return {
83
- action: isSettingInitialLocation ? 'INIT' : 'POP',
89
+ action,
84
90
  pathname,
85
91
  search,
86
92
  query: (0, _parseQueryFromSearch.default)(search),
@@ -96,7 +102,7 @@ class BrowserNavigation {
96
102
  // excluding ones that happened as a result of calling `.navigate()`.
97
103
  subscribe(listener) {
98
104
  const onPopState = () => {
99
- listener(this._createEntryFromCurrentLocation());
105
+ listener(this._createEntryFromCurrentLocation('SHIFT'));
100
106
  };
101
107
  window.addEventListener('popstate', onPopState);
102
108
  return () => {
@@ -120,7 +120,7 @@ class MemoryNavigation {
120
120
  }
121
121
  if (this._subscriptionListener) {
122
122
  this._subscriptionListener(this._createLocationObject({
123
- action: 'POP',
123
+ action: 'SHIFT',
124
124
  delta: this._index - prevIndex
125
125
  }));
126
126
  }
@@ -57,15 +57,21 @@ class BrowserNavigation {
57
57
  this._index = INITIAL_INDEX;
58
58
  }
59
59
  init() {
60
- return this._createEntryFromCurrentLocation();
60
+ return this._createEntryFromCurrentLocation('INIT');
61
61
  }
62
- _createEntryFromCurrentLocation() {
62
+ _createEntryFromCurrentLocation(action) {
63
63
  const {
64
64
  pathname,
65
65
  search,
66
66
  hash
67
67
  } = window.location;
68
68
  const isSettingInitialLocation = this._index === INITIAL_INDEX;
69
+ if (action === 'INIT' && !isSettingInitialLocation) {
70
+ throw Error('Browser session has already been initialized');
71
+ }
72
+ if (isSettingInitialLocation && action !== 'INIT') {
73
+ throw Error('Browser session must be initialized before reacting to location changes');
74
+ }
69
75
  const {
70
76
  key,
71
77
  index,
@@ -76,7 +82,7 @@ class BrowserNavigation {
76
82
  state: undefined
77
83
  }) : this._restoreAdditionalPropertiesForCurrentLocation();
78
84
  return {
79
- action: isSettingInitialLocation ? 'INIT' : 'POP',
85
+ action,
80
86
  pathname,
81
87
  search,
82
88
  query: parseQueryFromSearch(search),
@@ -92,7 +98,7 @@ class BrowserNavigation {
92
98
  // excluding ones that happened as a result of calling `.navigate()`.
93
99
  subscribe(listener) {
94
100
  const onPopState = () => {
95
- listener(this._createEntryFromCurrentLocation());
101
+ listener(this._createEntryFromCurrentLocation('SHIFT'));
96
102
  };
97
103
  window.addEventListener('popstate', onPopState);
98
104
  return () => {
@@ -116,7 +116,7 @@ class MemoryNavigation {
116
116
  }
117
117
  if (this._subscriptionListener) {
118
118
  this._subscriptionListener(this._createLocationObject({
119
- action: 'POP',
119
+ action: 'SHIFT',
120
120
  delta: this._index - prevIndex
121
121
  }));
122
122
  }
package/lib/index.d.ts CHANGED
@@ -15,7 +15,7 @@ export interface Location<TState = any> {
15
15
  /**
16
16
  * See the README on the `action` property of `location`.
17
17
  */
18
- action: 'PUSH' | 'REPLACE' | 'POP' | 'INIT';
18
+ action: 'PUSH' | 'REPLACE' | 'SHIFT' | 'INIT';
19
19
  /**
20
20
  * the path name; as on window.location e.g. '/foo'
21
21
  */
@@ -235,7 +235,9 @@ export interface Session {
235
235
  // This is just a copy-paste of the `session` interface above.
236
236
  declare abstract class SessionBase implements Session {
237
237
  navigation: SessionNavigation;
238
+
238
239
  dataStorage: SessionDataStorage;
240
+
239
241
  addBeforeDestroyListener(listener: BeforeDestroyListener): void;
240
242
 
241
243
  // These're internal variables that're manually set under the hood.
@@ -257,10 +259,7 @@ export class ServerSession extends SessionBase {
257
259
  }
258
260
 
259
261
  export class MemorySession extends SessionBase {
260
- constructor(
261
- initialLocation: InputLocation,
262
- options?: MemorySessionOptions,
263
- );
262
+ constructor(initialLocation: InputLocation, options?: MemorySessionOptions);
264
263
  }
265
264
 
266
265
  export const locationReducer: Reducer<Location, Action>;
@@ -269,6 +268,7 @@ export class LocationDataStorage {
269
268
  constructor(session: Session, options?: { namespace?: string });
270
269
 
271
270
  get(location: Location, key: string): any;
271
+
272
272
  set(location: Location, key: string, value: any): void;
273
273
  }
274
274
 
@@ -292,11 +292,7 @@ interface MiddlewareAPI<D extends Dispatch = Dispatch, S = any> {
292
292
  getState(): S;
293
293
  }
294
294
 
295
- interface Middleware<
296
- DispatchExt = {},
297
- S = any,
298
- D extends Dispatch = Dispatch,
299
- > {
295
+ interface Middleware<S = any, D extends Dispatch = Dispatch> {
300
296
  (api: MiddlewareAPI<D, S>): (next: Dispatch) => (action: any) => any;
301
297
  }
302
298
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "navigation-stack",
3
- "version": "0.2.0",
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
- "@definitelytyped/header-parser": "^0.0.41",
96
- "@definitelytyped/typescript-versions": "^0.0.41",
97
- "@definitelytyped/utils": "^0.0.41"
98
93
  }
99
94
  }
@@ -58,14 +58,24 @@ class BrowserNavigation {
58
58
  }
59
59
 
60
60
  init() {
61
- return this._createEntryFromCurrentLocation();
61
+ return this._createEntryFromCurrentLocation('INIT');
62
62
  }
63
63
 
64
- _createEntryFromCurrentLocation() {
64
+ _createEntryFromCurrentLocation(action) {
65
65
  const { pathname, search, hash } = window.location;
66
66
 
67
67
  const isSettingInitialLocation = this._index === INITIAL_INDEX;
68
68
 
69
+ if (action === 'INIT' && !isSettingInitialLocation) {
70
+ throw Error('Browser session has already been initialized');
71
+ }
72
+
73
+ if (isSettingInitialLocation && action !== 'INIT') {
74
+ throw Error(
75
+ 'Browser session must be initialized before reacting to location changes',
76
+ );
77
+ }
78
+
69
79
  const { key, index, delta, state } = isSettingInitialLocation
70
80
  ? this._createAdditionalPropertiesForNewLocation({
71
81
  delta: 1,
@@ -74,7 +84,7 @@ class BrowserNavigation {
74
84
  : this._restoreAdditionalPropertiesForCurrentLocation();
75
85
 
76
86
  return {
77
- action: isSettingInitialLocation ? 'INIT' : 'POP',
87
+ action,
78
88
  pathname,
79
89
  search,
80
90
  query: parseQueryFromSearch(search),
@@ -90,7 +100,7 @@ class BrowserNavigation {
90
100
  // excluding ones that happened as a result of calling `.navigate()`.
91
101
  subscribe(listener) {
92
102
  const onPopState = () => {
93
- listener(this._createEntryFromCurrentLocation());
103
+ listener(this._createEntryFromCurrentLocation('SHIFT'));
94
104
  };
95
105
 
96
106
  window.addEventListener('popstate', onPopState);
@@ -118,7 +118,7 @@ class MemoryNavigation {
118
118
  if (this._subscriptionListener) {
119
119
  this._subscriptionListener(
120
120
  this._createLocationObject({
121
- action: 'POP',
121
+ action: 'SHIFT',
122
122
  delta: this._index - prevIndex,
123
123
  }),
124
124
  );
@@ -94,7 +94,7 @@ describe('createBeforeLocationChangeListenerMiddleware', () => {
94
94
  });
95
95
  });
96
96
 
97
- describe('POP navigations', () => {
97
+ describe('SHIFT navigations', () => {
98
98
  beforeEach(() => {
99
99
  store.dispatch(Actions.push('/bar'));
100
100
  });
@@ -109,7 +109,7 @@ describe('createBeforeLocationChangeListenerMiddleware', () => {
109
109
  expect(listener).to.have.been.calledOnce();
110
110
 
111
111
  expect(listener.firstCall.args[0]).to.include({
112
- action: 'POP',
112
+ action: 'SHIFT',
113
113
  pathname: '/foo',
114
114
  delta: -1,
115
115
  });
@@ -210,7 +210,7 @@ describe('createNavigationBlockerMiddleware', () => {
210
210
  });
211
211
  });
212
212
 
213
- describe('POP navigations', () => {
213
+ describe('SHIFT navigations', () => {
214
214
  beforeEach(() => {
215
215
  store.dispatch(Actions.push('/bar'));
216
216
  });
@@ -223,7 +223,7 @@ describe('createNavigationBlockerMiddleware', () => {
223
223
  expect(store.getState().pathname).to.equal('/foo');
224
224
 
225
225
  expect(blocker.firstCall.args[0]).to.include({
226
- action: 'POP',
226
+ action: 'SHIFT',
227
227
  pathname: '/foo',
228
228
  delta: -1,
229
229
  });
@@ -309,7 +309,7 @@ describe('createNavigationBlockerMiddleware', () => {
309
309
 
310
310
  store.dispatch(Actions.shift(-1));
311
311
 
312
- // session popped, update to store blocked.
312
+ // session shifted, update to store blocked.
313
313
  expect(session.navigation.init().pathname).to.equal('/foo');
314
314
  expect(store.getState().pathname).to.equal('/bar');
315
315
 
@@ -326,7 +326,7 @@ describe('createNavigationBlockerMiddleware', () => {
326
326
  sessionDeferred.resolve();
327
327
  await delay(10);
328
328
 
329
- // session re-popped, update to store delayed.
329
+ // session re-shifted (the rewind was undone), update to store delayed.
330
330
  expect(session.navigation.init().pathname).to.equal('/foo');
331
331
  expect(store.getState().pathname).to.equal('/foo');
332
332
  });
@@ -335,7 +335,7 @@ describe('createNavigationBlockerMiddleware', () => {
335
335
  const deferred = pDefer();
336
336
  addNavigationBlocker(() => deferred.promise);
337
337
 
338
- // Update location with a `POP` action.
338
+ // Update location with a `SHIFT` action.
339
339
  /* eslint-disable no-underscore-dangle */
340
340
  session.navigation._index = 0;
341
341
  session.navigation._subscriptionListener(session.navigation.init(null));
@@ -354,7 +354,7 @@ describe('createNavigationBlockerMiddleware', () => {
354
354
  // const deferred = pDefer();
355
355
  // addNavigationBlocker(() => deferred.promise);
356
356
  //
357
- // // Update location with a `POP` action.
357
+ // // Update location with a `SHIFT` action.
358
358
  // /* eslint-disable no-underscore-dangle */
359
359
  // session.navigation._index = 0;
360
360
  // session.navigation._subscriptionListener(session.navigation.init(null));
@@ -116,7 +116,7 @@ describe('BrowserSession', () => {
116
116
  });
117
117
  expect(listener).to.have.been.calledOnce();
118
118
  expect(listener.firstCall.args[0]).to.deep.include({
119
- action: 'POP',
119
+ action: 'SHIFT',
120
120
  pathname: '/bar',
121
121
  search: '?search',
122
122
  hash: '#hash',
@@ -133,7 +133,7 @@ describe('BrowserSession', () => {
133
133
  expect(window.location.pathname).to.equal('/foo');
134
134
  expect(listener).to.have.been.calledOnce();
135
135
  expect(listener.firstCall.args[0]).to.deep.include({
136
- action: 'POP',
136
+ action: 'SHIFT',
137
137
  pathname: '/foo',
138
138
  index: 0,
139
139
  delta: -1,
@@ -167,7 +167,7 @@ describe('BrowserSession', () => {
167
167
 
168
168
  expect(listener).to.have.been.calledOnce();
169
169
  expect(listener.firstCall.args[0]).to.include({
170
- action: 'POP',
170
+ action: 'SHIFT',
171
171
  pathname: '/bar',
172
172
  });
173
173
  listener.resetHistory();
@@ -62,7 +62,7 @@ describe('MemorySession', () => {
62
62
 
63
63
  expect(listener).to.have.been.calledOnce();
64
64
  expect(listener.firstCall.args[0]).to.deep.include({
65
- action: 'POP',
65
+ action: 'SHIFT',
66
66
  pathname: '/bar',
67
67
  key: barLocation.key,
68
68
  index: 1,
@@ -83,7 +83,7 @@ describe('MemorySession', () => {
83
83
 
84
84
  expect(listener).to.have.been.calledOnce();
85
85
  expect(listener.firstCall.args[0]).to.include({
86
- action: 'POP',
86
+ action: 'SHIFT',
87
87
  pathname: '/bar',
88
88
  });
89
89
  listener.resetHistory();
@@ -107,7 +107,7 @@ describe('MemorySession', () => {
107
107
 
108
108
  expect(listener).to.have.been.calledOnce();
109
109
  expect(listener.firstCall.args[0]).to.include({
110
- action: 'POP',
110
+ action: 'SHIFT',
111
111
  pathname: '/foo',
112
112
  delta: -2,
113
113
  });
@@ -121,7 +121,7 @@ describe('MemorySession', () => {
121
121
 
122
122
  expect(listener).to.have.been.calledOnce();
123
123
  expect(listener.firstCall.args[0]).to.include({
124
- action: 'POP',
124
+ action: 'SHIFT',
125
125
  pathname: '/baz',
126
126
  delta: 2,
127
127
  });
@@ -146,7 +146,7 @@ describe('MemorySession', () => {
146
146
 
147
147
  expect(listener).to.have.been.calledOnce();
148
148
  expect(listener.firstCall.args[0]).to.include({
149
- action: 'POP',
149
+ action: 'SHIFT',
150
150
  pathname: '/bar',
151
151
  delta: 1,
152
152
  });
package/types/index.d.ts CHANGED
@@ -15,7 +15,7 @@ export interface Location<TState = any> {
15
15
  /**
16
16
  * See the README on the `action` property of `location`.
17
17
  */
18
- action: 'PUSH' | 'REPLACE' | 'POP' | 'INIT';
18
+ action: 'PUSH' | 'REPLACE' | 'SHIFT' | 'INIT';
19
19
  /**
20
20
  * the path name; as on window.location e.g. '/foo'
21
21
  */
@@ -235,7 +235,9 @@ export interface Session {
235
235
  // This is just a copy-paste of the `session` interface above.
236
236
  declare abstract class SessionBase implements Session {
237
237
  navigation: SessionNavigation;
238
+
238
239
  dataStorage: SessionDataStorage;
240
+
239
241
  addBeforeDestroyListener(listener: BeforeDestroyListener): void;
240
242
 
241
243
  // These're internal variables that're manually set under the hood.
@@ -257,10 +259,7 @@ export class ServerSession extends SessionBase {
257
259
  }
258
260
 
259
261
  export class MemorySession extends SessionBase {
260
- constructor(
261
- initialLocation: InputLocation,
262
- options?: MemorySessionOptions,
263
- );
262
+ constructor(initialLocation: InputLocation, options?: MemorySessionOptions);
264
263
  }
265
264
 
266
265
  export const locationReducer: Reducer<Location, Action>;
@@ -269,6 +268,7 @@ export class LocationDataStorage {
269
268
  constructor(session: Session, options?: { namespace?: string });
270
269
 
271
270
  get(location: Location, key: string): any;
271
+
272
272
  set(location: Location, key: string, value: any): void;
273
273
  }
274
274
 
@@ -292,11 +292,7 @@ interface MiddlewareAPI<D extends Dispatch = Dispatch, S = any> {
292
292
  getState(): S;
293
293
  }
294
294
 
295
- interface Middleware<
296
- DispatchExt = {},
297
- S = any,
298
- D extends Dispatch = Dispatch,
299
- > {
295
+ interface Middleware<S = any, D extends Dispatch = Dispatch> {
300
296
  (api: MiddlewareAPI<D, S>): (next: Dispatch) => (action: any) => any;
301
297
  }
302
298