navigation-stack 0.4.0 → 0.5.1

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 (52) hide show
  1. package/CHANGELOG.md +12 -0
  2. package/README.md +20 -12
  3. package/data-storage/package.json +2 -1
  4. package/lib/cjs/NavigationStack.js +17 -2
  5. package/lib/cjs/debug.js +12 -0
  6. package/lib/cjs/getLocationFromInternalLocation.js +2 -2
  7. package/lib/cjs/navigationBlockers.js +3 -0
  8. package/lib/cjs/scroll-position/ScrollPositionAutoSaver.js +13 -2
  9. package/lib/cjs/scroll-position/ScrollPositionRestoration.js +37 -13
  10. package/lib/cjs/scroll-position/ScrollPositionSaver.js +8 -2
  11. package/lib/cjs/session/Session.js +6 -0
  12. package/lib/cjs/session/navigation/operation/operations.js +4 -4
  13. package/lib/esm/NavigationStack.js +17 -2
  14. package/lib/esm/debug.js +7 -0
  15. package/lib/esm/getLocationFromInternalLocation.js +2 -2
  16. package/lib/esm/navigationBlockers.js +3 -0
  17. package/lib/esm/scroll-position/ScrollPositionAutoSaver.js +13 -2
  18. package/lib/esm/scroll-position/ScrollPositionRestoration.js +37 -13
  19. package/lib/esm/scroll-position/ScrollPositionSaver.js +8 -2
  20. package/lib/esm/session/Session.js +6 -0
  21. package/lib/esm/session/navigation/operation/operations.js +4 -4
  22. package/lib/index.d.ts +10 -9
  23. package/lib/scroll-position/index.d.ts +11 -11
  24. package/package.json +1 -1
  25. package/redux/package.json +2 -1
  26. package/scroll-position/package.json +2 -1
  27. package/src/NavigationStack.js +18 -2
  28. package/src/debug.js +8 -0
  29. package/src/getLocationFromInternalLocation.js +2 -2
  30. package/src/navigationBlockers.js +3 -0
  31. package/src/scroll-position/ScrollPositionAutoSaver.js +19 -2
  32. package/src/scroll-position/ScrollPositionRestoration.js +100 -53
  33. package/src/scroll-position/ScrollPositionSaver.js +21 -1
  34. package/src/session/Session.js +22 -0
  35. package/src/session/navigation/operation/operations.js +4 -4
  36. package/test/NavigationStack.test.js +130 -27
  37. package/test/middlewareTestUtil.js +1 -1
  38. package/test/redux/locationReducer.test.js +1 -1
  39. package/test/redux/middleware/createNonProgrammaticNavigationBlockerMiddleware.test.js +5 -5
  40. package/test/redux/middleware/createProgrammaticNavigationBlockerMiddleware.test.js +2 -2
  41. package/test/redux/middleware/navigationOperationMiddleware.test.js +2 -2
  42. package/test/scroll-position/ScrollPositionRestoration.test.js +78 -61
  43. package/test/scroll-position/addScrollableContainer.js +5 -2
  44. package/test/scroll-position/{addScrollableContainerWithHyperlink.js → addScrollableContainerWithAnchors.js} +8 -2
  45. package/test/scroll-position/createApp.js +28 -7
  46. package/test/scroll-position/withScrollableContainerAtIndexPageWithDisabledAutomaticScrollPositionRestoration.js +72 -0
  47. package/test/session/InMemorySession.test.js +28 -28
  48. package/test/session/ServerSession.test.js +1 -1
  49. package/test/session/WebBrowserSession.test.js +17 -17
  50. package/types/index.d.ts +10 -9
  51. package/types/scroll-position/index.d.ts +11 -11
  52. package/test/scroll-position/withScrollableContainerAtIndexPage.js +0 -62
@@ -1,5 +1,6 @@
1
1
  /* eslint-disable no-underscore-dangle */
2
2
 
3
+ import debug from './debug';
3
4
  import isPromise from './isPromise';
4
5
  export function getNavigationBlockers(session) {
5
6
  return session._navigationBlockersList || [];
@@ -72,12 +73,14 @@ export function runNavigationBlockers(navigationBlockers, toLocation) {
72
73
  if (isPromise(result)) {
73
74
  return result.then(resultValue => {
74
75
  if (resultValue) {
76
+ debug('Navigation blocked', toLocation.pathname);
75
77
  return resultValue;
76
78
  }
77
79
  return next();
78
80
  });
79
81
  }
80
82
  if (result) {
83
+ debug('Navigation blocked', toLocation.pathname);
81
84
  return result;
82
85
  }
83
86
  return next();
@@ -2,6 +2,7 @@
2
2
 
3
3
  import { PAGE_SCROLLABLE_CONTAINER_KEY } from './constants';
4
4
  import scheduleNextTick from './scheduleNextTick';
5
+ import debug from '../debug';
5
6
  export default class ScrollPositionAutoSaver {
6
7
  constructor({
7
8
  scrollPosition,
@@ -57,15 +58,21 @@ export default class ScrollPositionAutoSaver {
57
58
  }
58
59
  }
59
60
  }
60
- cancelSavePageScrollPosition() {
61
+ cancelSavePageScrollPosition(hasRun) {
61
62
  if (this._cancelSavePageScrollPosition) {
63
+ if (!hasRun) {
64
+ debug('cancel delayed save scroll position', PAGE_SCROLLABLE_CONTAINER_KEY);
65
+ }
62
66
  this._cancelSavePageScrollPosition();
63
67
  this._cancelSavePageScrollPosition = null;
64
68
  }
65
69
  }
66
- cancelSaveScrollableContainerScrollPosition(scrollableContainerKey) {
70
+ cancelSaveScrollableContainerScrollPosition(scrollableContainerKey, hasRun) {
67
71
  const scrollableContainerEntry = this._getScrollableContainers()[scrollableContainerKey];
68
72
  if (scrollableContainerEntry.cancelSaveScrollPosition) {
73
+ if (!hasRun) {
74
+ debug('cancel delayed save scroll position', scrollableContainerKey);
75
+ }
69
76
  scrollableContainerEntry.cancelSaveScrollPosition();
70
77
  scrollableContainerEntry.cancelSaveScrollPosition = null;
71
78
  }
@@ -96,7 +103,9 @@ export default class ScrollPositionAutoSaver {
96
103
  // because there might be too many in a given short period of time
97
104
  // which could affect the performance of the application.
98
105
  if (!scrollableContainerEntry.cancelSaveScrollPosition) {
106
+ debug('scroll detected', scrollableContainerKey);
99
107
  scrollableContainerEntry.cancelSaveScrollPosition = scheduleNextTick(() => {
108
+ debug('auto-save scroll position after scroll', scrollableContainerKey);
100
109
  this._scrollPositionSaver.saveScrollableContainerScrollPosition(scrollableContainerKey, scrollableContainerEntry.scrollableContainer);
101
110
  });
102
111
  }
@@ -105,6 +114,8 @@ export default class ScrollPositionAutoSaver {
105
114
  addPageScrollListener() {
106
115
  // Set up scroll listener on the page.
107
116
  this._removePageScrollListener = this._scrollPosition.addPageScrollListener(() => {
117
+ debug('scroll detected', PAGE_SCROLLABLE_CONTAINER_KEY);
118
+
108
119
  // This flag is not used in real life and is only used in tests (for some reason).
109
120
  if (!this._shouldSaveScrollPosition()) {
110
121
  return;
@@ -5,6 +5,7 @@ import ScrollPositionSaver from './ScrollPositionSaver';
5
5
  import ScrollPositionSetter from './ScrollPositionSetter';
6
6
  import { PAGE_SCROLLABLE_CONTAINER_KEY } from './constants';
7
7
  import LocationDataStorage from '../data-storage/LocationDataStorage';
8
+ import debug from '../debug';
8
9
  function areEqualScrollPositions(scrollPosition1, scrollPosition2) {
9
10
  let i = 0;
10
11
  while (i < scrollPosition1.length) {
@@ -48,8 +49,10 @@ export default class ScrollPositionRestoration {
48
49
  running
49
50
  }) => {
50
51
  if (running) {
52
+ debug('▶ running');
51
53
  this._disableAutomaticScrollRestoration();
52
54
  } else {
55
+ debug('⏹ not running');
53
56
  this._enableAutomaticScrollRestoration();
54
57
 
55
58
  // There might be previous scroll position already saved in the data storage.
@@ -125,11 +128,11 @@ export default class ScrollPositionRestoration {
125
128
  // This function is only used in tests.
126
129
  // There seems to be no use of it in real life, hence it's not public API.
127
130
  // It's only used in tests.
128
- _getScrollPositionForLocation: _options && _options._getPageScrollPositionForLocation,
131
+ _getSavedScrollPositionOnLocationChange: _options && _options._getSavedPageScrollPositionOnLocationChange,
129
132
  // This function is only used in tests.
130
133
  // There seems to be no use of it in real life, hence it's not public API.
131
134
  // It's only used in tests.
132
- _shouldUpdateScrollPositionForLocation: _options && _options._shouldUpdatePageScrollPositionForLocation
135
+ _shouldSetScrollPositionOnLocationChange: _options && _options._shouldSetPageScrollPositionOnLocationChange
133
136
  };
134
137
  }
135
138
  addScrollableContainer(scrollableContainerKey, scrollableContainer, _options) {
@@ -140,10 +143,17 @@ export default class ScrollPositionRestoration {
140
143
  // this._scrollableContainerKeyCounter++;
141
144
  // const scrollableContainerKey = String(this._scrollableContainerKeyCounter);
142
145
 
146
+ // Validate `scrollableContainerKey`.
143
147
  if (scrollableContainerKey === PAGE_SCROLLABLE_CONTAINER_KEY) {
144
148
  throw new Error(`Scrollable container key "${scrollableContainerKey}" is not allowed`);
145
149
  }
146
150
 
151
+ // Check that it hasn't already been added.
152
+ if (this._scrollableContainers[scrollableContainerKey]) {
153
+ throw new Error(`Scrollable container key "${scrollableContainerKey}" is already added`);
154
+ }
155
+ debug('add scrollable container', scrollableContainerKey);
156
+
147
157
  // Add scrollable container entry.
148
158
  this._scrollableContainers[scrollableContainerKey] = {
149
159
  // Scrollable container element.
@@ -157,11 +167,11 @@ export default class ScrollPositionRestoration {
157
167
  // This function is only used in tests.
158
168
  // There seems to be no use of it in real life, hence it's not public API.
159
169
  // It's only used in tests.
160
- _shouldUpdateScrollPositionForLocation: _options && _options._shouldUpdateScrollPositionForLocation,
170
+ _shouldSetScrollPositionOnLocationChange: _options && _options._shouldSetScrollPositionOnLocationChange,
161
171
  // This function is only used in tests.
162
172
  // There seems to be no use of it in real life, hence it's not public API.
163
173
  // It's only used in tests.
164
- _getScrollPositionForLocation: _options && _options._getScrollPositionForLocation
174
+ _getSavedScrollPositionOnLocationChange: _options && _options._getSavedScrollPositionOnLocationChange
165
175
  };
166
176
 
167
177
  // Scrollable containers could be added at any time, including page mount.
@@ -173,8 +183,10 @@ export default class ScrollPositionRestoration {
173
183
  if (this._location) {
174
184
  const previouslySavedScrollPosition = this._getSavedScrollPositionForLocation(this._location, scrollableContainerKey);
175
185
  if (previouslySavedScrollPosition) {
186
+ debug('restore scroll position on add scrollable container', this._location.pathname, scrollableContainerKey, previouslySavedScrollPosition);
176
187
  this._scrollPosition.setScrollableContainerScrollPosition(scrollableContainer, previouslySavedScrollPosition);
177
188
  } else {
189
+ debug('save scroll position on add scrollable container', this._location.pathname, scrollableContainerKey);
178
190
  this._scrollPositionSaver.saveScrollableContainerScrollPosition(scrollableContainerKey, scrollableContainer);
179
191
  }
180
192
  }
@@ -184,6 +196,7 @@ export default class ScrollPositionRestoration {
184
196
 
185
197
  // Removes the scrollable container.
186
198
  return () => {
199
+ debug('remove scrollable container', scrollableContainerKey);
187
200
  this._scrollPositionSaver._scrollPositionAutoSaver.cancelSaveScrollableContainerScrollPosition(scrollableContainerKey);
188
201
  this._scrollPositionSaver._scrollPositionAutoSaver.removeScrollableContainerScrollListener(scrollableContainerKey);
189
202
  delete this._scrollableContainers[scrollableContainerKey];
@@ -242,14 +255,20 @@ export default class ScrollPositionRestoration {
242
255
  //
243
256
  // // Save the current scroll position on the current page while it's still rendered.
244
257
  // // This saved scroll position could later be restored in case of returing to this page.
258
+ // // Even if the current scroll position is a default one (scrolled to top), it should still
259
+ // // be saved in order to overwrite any potential previously-saved non-default scroll position.
245
260
  // this._scrollPositionSaver.saveScrollPosition();
246
261
  // };
247
262
 
263
+ // Should be called whenever a different location has been rendered (i.e. immediately after).
264
+ // Returns a Promise that resolves when finished restoring scroll position.
265
+ // There's no need to await for that Promise. It's just there because it exists.
248
266
  locationRendered(location) {
249
267
  // Validate that `location` has a `key`.
250
268
  if (!location.key) {
251
269
  throw new Error('`location` must have a `key`');
252
270
  }
271
+ debug('rendered location', location.pathname);
253
272
  this._prevLocation = this._location;
254
273
  this._location = location;
255
274
  this._scrollPosition.init();
@@ -280,7 +299,7 @@ export default class ScrollPositionRestoration {
280
299
 
281
300
  // Set the scroll position for the new page:
282
301
  // either restore a previously-saved one or set it to a default scroll position.
283
- this._setScrollPosition();
302
+ return this._setScrollPosition();
284
303
  }
285
304
 
286
305
  // Tells if the current scroll position is the default one.
@@ -299,16 +318,20 @@ export default class ScrollPositionRestoration {
299
318
  }
300
319
  return true;
301
320
  }
321
+
322
+ // Restores scroll position.
323
+ // Returns a Promise that resolves when finished setting scroll position.
324
+ // There's no need to await for this Promise. It just exists.
302
325
  _setScrollPosition() {
303
- for (const scrollableContainerKey of Object.keys(this._scrollableContainers)) {
326
+ return Promise.all(Object.keys(this._scrollableContainers).map(scrollableContainerKey => {
304
327
  const scrollableContainerEntry = this._scrollableContainers[scrollableContainerKey];
305
328
 
306
329
  // This function is only used in tests.
307
330
  // There seems to be no use of it in real life, hence it's not public API.
308
331
  // It's only used in tests.
309
- if (scrollableContainerEntry._shouldUpdateScrollPositionForLocation) {
310
- if (!scrollableContainerEntry._shouldUpdateScrollPositionForLocation(this._location, this._prevLocation)) {
311
- continue;
332
+ if (scrollableContainerEntry._shouldSetScrollPositionOnLocationChange) {
333
+ if (!scrollableContainerEntry._shouldSetScrollPositionOnLocationChange(this._location, this._prevLocation)) {
334
+ return Promise.resolve();
312
335
  }
313
336
  }
314
337
 
@@ -318,18 +341,19 @@ export default class ScrollPositionRestoration {
318
341
  // This function is only used in tests.
319
342
  // There seems to be no use of it in real life, hence it's not public API.
320
343
  // It's only used in tests.
321
- if (scrollableContainerEntry._getScrollPositionForLocation) {
322
- scrollPositionOrAnchorToSet = scrollableContainerEntry._getScrollPositionForLocation(this._location, this._prevLocation);
344
+ if (scrollableContainerEntry._getSavedScrollPositionOnLocationChange) {
345
+ scrollPositionOrAnchorToSet = scrollableContainerEntry._getSavedScrollPositionOnLocationChange(this._location, this._prevLocation);
323
346
  }
324
347
 
325
348
  // Get scroll position (or anchor) to set.
326
349
  if (!scrollPositionOrAnchorToSet) {
327
350
  scrollPositionOrAnchorToSet = scrollableContainerKey === PAGE_SCROLLABLE_CONTAINER_KEY ? this._getPageScrollPositionOrAnchorToSet(this._location) : this._getScrollableContainerScrollPositionToSet(this._location, scrollableContainerKey);
328
351
  }
352
+ debug('restore scroll position', this._location.pathname, scrollableContainerKey, scrollPositionOrAnchorToSet);
329
353
 
330
354
  // Set scroll position of scrollable container.
331
- scrollableContainerEntry.scrollPositionSetter.set(scrollableContainerEntry.scrollableContainer, scrollPositionOrAnchorToSet, this._scrollPosition);
332
- }
355
+ return scrollableContainerEntry.scrollPositionSetter.set(scrollableContainerEntry.scrollableContainer, scrollPositionOrAnchorToSet, this._scrollPosition);
356
+ }));
333
357
  }
334
358
  _getSavedScrollPositionForLocation(location, scrollableContainerKey = PAGE_SCROLLABLE_CONTAINER_KEY) {
335
359
  return this._locationDataStorage.get(location, scrollableContainerKey);
@@ -2,6 +2,7 @@
2
2
 
3
3
  import ScrollPositionAutoSaver from './ScrollPositionAutoSaver';
4
4
  import { PAGE_SCROLLABLE_CONTAINER_KEY } from './constants';
5
+ import debug from '../debug';
5
6
  export default class ScrollPositionSaver {
6
7
  constructor({
7
8
  scrollPosition,
@@ -36,6 +37,7 @@ export default class ScrollPositionSaver {
36
37
  if (!this._shouldSaveScrollPosition()) {
37
38
  return;
38
39
  }
40
+ debug('save scroll position', this._getLocation().pathname);
39
41
 
40
42
  // Get scrollable containers.
41
43
  const scrollableContainers = this._getScrollableContainers();
@@ -50,23 +52,27 @@ export default class ScrollPositionSaver {
50
52
  }
51
53
  }
52
54
  savePageScrollPosition() {
55
+ debug('save scroll position', this._getLocation().pathname, PAGE_SCROLLABLE_CONTAINER_KEY, this._scrollPosition.getPageScrollPosition());
56
+
53
57
  // * If this is not a scheduled "auto-save" of scroll position
54
58
  // and there already exists any scheduled "auto-save" of scroll position,
55
59
  // cancel it and save scroll position right now instead.
56
60
  // * If this is a scheduled "auto-save" of scroll position,
57
61
  // clear the "cancel" function because it's no longer of use.
58
- this._scrollPositionAutoSaver.cancelSavePageScrollPosition();
62
+ this._scrollPositionAutoSaver.cancelSavePageScrollPosition(true);
59
63
 
60
64
  // Save scroll position.
61
65
  this._saveScrollPositionForLocation(this._getLocation(), undefined, this._scrollPosition.getPageScrollPosition());
62
66
  }
63
67
  saveScrollableContainerScrollPosition(scrollableContainerKey, scrollableContainer) {
68
+ debug('save scroll position', this._getLocation().pathname, scrollableContainerKey, this._scrollPosition.getScrollableContainerScrollPosition(scrollableContainer));
69
+
64
70
  // * If this is not a scheduled "auto-save" of scroll position
65
71
  // and there already exists any scheduled "auto-save" of scroll position,
66
72
  // cancel it and save scroll position right now instead.
67
73
  // * If this is a scheduled "auto-save" of scroll position,
68
74
  // clear the "cancel" function because it's no longer of use.
69
- this._scrollPositionAutoSaver.cancelSaveScrollableContainerScrollPosition(scrollableContainerKey);
75
+ this._scrollPositionAutoSaver.cancelSaveScrollableContainerScrollPosition(scrollableContainerKey, true);
70
76
 
71
77
  // Save scroll position.
72
78
  this._saveScrollPositionForLocation(this._getLocation(), scrollableContainerKey, this._scrollPosition.getScrollableContainerScrollPosition(scrollableContainer));
@@ -1,3 +1,4 @@
1
+ import debug from '../debug';
1
2
  import parseInputLocation from '../parseInputLocation';
2
3
  import createSessionKey from './key/createSessionKey';
3
4
  import NavigationOutOfBoundsError from './navigation/error/NavigationOutOfBoundsError';
@@ -51,6 +52,7 @@ export default class Session {
51
52
  // but if it was possible, this call would be required. It would also be required
52
53
  // by `navigation` to call `session.getNextKey()` function to increment `locationKeyIndex`.
53
54
  this._updateTerminalLocationIndex(location);
55
+ debug('current location', location.pathname, 'index', this._currentLocationIndex);
54
56
  });
55
57
  }
56
58
 
@@ -90,6 +92,7 @@ export default class Session {
90
92
  if (this._currentLocationIndex !== INITIAL_INDEX) {
91
93
  throw new Error('Already started');
92
94
  }
95
+ debug('▶ start session', initialLocation.pathname);
93
96
  this._started = true;
94
97
  const key = this._getNextLocationKey();
95
98
  const index = INITIAL_INDEX + 1;
@@ -108,6 +111,7 @@ export default class Session {
108
111
  if (this._stopped) {
109
112
  throw Error('Already stopped');
110
113
  }
114
+ debug('⏹ stop session');
111
115
 
112
116
  // Once stopped, it won't be able to be restarted.
113
117
  this._stopped = true;
@@ -133,6 +137,7 @@ export default class Session {
133
137
  });
134
138
  const key = this._getNextLocationKey();
135
139
  const index = this._currentLocationIndex + delta;
140
+ debug(operation === NavigationOperations.PUSH ? '↓' : '⇅', operation, location.pathname, 'index', index);
136
141
 
137
142
  // Navigate to the location.
138
143
  const locationResult = this._navigation.navigate(location, {
@@ -155,6 +160,7 @@ export default class Session {
155
160
  return;
156
161
  }
157
162
  const index = this._currentLocationIndex + delta;
163
+ debug(delta > 0 ? '→' : '←', 'shift', delta, 'index', index);
158
164
 
159
165
  // Validate that the new `index` is not out of bounds.
160
166
  if (index < 0 || index > this._terminalLocationIndex) {
@@ -1,6 +1,6 @@
1
1
  export default {
2
- INIT: 'INIT',
3
- PUSH: 'PUSH',
4
- REPLACE: 'REPLACE',
5
- SHIFT: 'SHIFT'
2
+ INIT: 'init',
3
+ PUSH: 'push',
4
+ REPLACE: 'replace',
5
+ SHIFT: 'shift'
6
6
  };
package/lib/index.d.ts CHANGED
@@ -35,22 +35,23 @@ export interface Location extends LocationBase {
35
35
  * a unique key identifying the current history entry
36
36
  */
37
37
  key: string;
38
+
39
+ /**
40
+ * the current index of the history entry, starting at 0 for the initial
41
+ * entry; this increments on `.push()` but not on `.replace()`
42
+ */
43
+ index: number;
38
44
  }
39
45
 
40
- type PushOrReplaceOperation = 'PUSH' | 'REPLACE';
46
+ type PushOrReplaceOperation = 'push' | 'replace';
41
47
 
42
48
  export interface LocationInternal extends Location {
43
49
  /**
44
50
  * `navigation-stack` operation.
45
51
  */
46
- operation: PushOrReplaceOperation | 'SHIFT' | 'INIT';
47
- /**
48
- * the current index of the history entry, starting at 0 for the initial
49
- * entry; this increments on `.push()` but not on `.replace()`
50
- */
51
- index: number;
52
+ operation: PushOrReplaceOperation | 'shift' | 'init';
52
53
  /**
53
- * the difference between the current index and the index of the previous location
54
+ * the difference between the index of the current location and the index of the previous location.
54
55
  */
55
56
  delta: number;
56
57
  }
@@ -155,7 +156,7 @@ export class NavigationStack<ScrollableContainer = any, Anchor = any> {
155
156
 
156
157
  shift(delta: number): void;
157
158
 
158
- locationRendered(): void;
159
+ locationRendered(): Promise<void>;
159
160
 
160
161
  stop(): void;
161
162
  }
@@ -15,21 +15,21 @@ export class ScrollPositionRestoration<
15
15
 
16
16
  // `_options` are currently only used in tests.
17
17
  _options?: {
18
- // `_options._shouldUpdatePageScrollPositionForLocation`
18
+ // `_options._shouldSetPageScrollPositionOnLocationChange`
19
19
  // isn't used in real life and is not part of the public API.
20
20
  // It's only used in tests.
21
- _shouldUpdatePageScrollPositionForLocation?: (
21
+ _shouldSetPageScrollPositionOnLocationChange?: (
22
22
  location: Location,
23
23
  prevLocation: Location | undefined,
24
24
  ) => boolean;
25
25
 
26
- // `_options._getPageScrollPositionForLocation`
26
+ // `_options._getSavedPageScrollPositionOnLocationChange`
27
27
  // isn't used in real life and is not part of the public API.
28
28
  // It's only used in tests.
29
- _getPageScrollPositionForLocation?: (
29
+ _getSavedPageScrollPositionOnLocationChange?: (
30
30
  location: Location,
31
31
  prevLocation: Location | undefined,
32
- ) => boolean;
32
+ ) => [number, number] | undefined;
33
33
 
34
34
  // Using this option, a developer could theoretically provide their own implementation
35
35
  // of setting a scroll position. For example, it could use "smooth" (animated) scrolling, etc.
@@ -47,21 +47,21 @@ export class ScrollPositionRestoration<
47
47
 
48
48
  // `_options` are currently only used in tests.
49
49
  _options?: {
50
- // `_options._shouldUpdateScrollPositionForLocation`
50
+ // `_options._shouldSetScrollPositionOnLocationChange`
51
51
  // isn't used in real life and is not part of the public API.
52
52
  // It's only used in tests.
53
- _shouldUpdateScrollPositionForLocation?: (
53
+ _shouldSetScrollPositionOnLocationChange?: (
54
54
  location: Location,
55
55
  prevLocation: Location | undefined,
56
56
  ) => boolean;
57
57
 
58
- // `_options._getScrollPositionForLocation`
58
+ // `_options._getSavedScrollPositionOnLocationChange`
59
59
  // isn't used in real life and is not part of the public API.
60
60
  // It's only used in tests.
61
- _getScrollPositionForLocation?: (
61
+ _getSavedScrollPositionOnLocationChange?: (
62
62
  location: Location,
63
63
  prevLocation: Location | undefined,
64
- ) => boolean;
64
+ ) => [number, number] | undefined;
65
65
 
66
66
  // Using this option, a developer could theoretically provide their own implementation
67
67
  // of setting a scroll position. For example, it could use "smooth" (animated) scrolling, etc.
@@ -70,7 +70,7 @@ export class ScrollPositionRestoration<
70
70
  },
71
71
  ): () => void;
72
72
 
73
- locationRendered: (location: Location) => void;
73
+ locationRendered: (location: Location) => Promise<void>;
74
74
 
75
75
  stop(): void;
76
76
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "navigation-stack",
3
- "version": "0.4.0",
3
+ "version": "0.5.1",
4
4
  "description": "Handles navigation in a web browser",
5
5
  "keywords": [
6
6
  "history",
@@ -2,5 +2,6 @@
2
2
  "private": true,
3
3
  "name": "navigation-stack/redux",
4
4
  "main": "./../lib/cjs/redux/index.js",
5
- "module": "./../lib/esm/redux/index.js"
5
+ "module": "./../lib/esm/redux/index.js",
6
+ "types": "./../lib/redux/index.d.ts"
6
7
  }
@@ -2,5 +2,6 @@
2
2
  "private": true,
3
3
  "name": "navigation-stack/scroll-position",
4
4
  "main": "./../lib/cjs/scroll-position/index.js",
5
- "module": "./../lib/esm/scroll-position/index.js"
5
+ "module": "./../lib/esm/scroll-position/index.js",
6
+ "types": "./../lib/scroll-position/index.d.ts"
6
7
  }
@@ -5,6 +5,15 @@ import createMiddlewares from './redux/createMiddlewares';
5
5
  import locationReducer from './redux/locationReducer';
6
6
  import ScrollPositionRestoration from './scroll-position/ScrollPositionRestoration';
7
7
 
8
+ function getCreateMiddlewaresOptions(navigationStackOptions) {
9
+ if (!navigationStackOptions) {
10
+ return undefined;
11
+ }
12
+ // eslint-disable-next-line no-unused-vars
13
+ const { maintainScrollPosition, ...restOptions } = navigationStackOptions;
14
+ return restOptions;
15
+ }
16
+
8
17
  export default class NavigationStack {
9
18
  constructor(session, options) {
10
19
  this._session = session;
@@ -12,7 +21,9 @@ export default class NavigationStack {
12
21
  // Create a Redux store.
13
22
  this._store = createStore(
14
23
  locationReducer,
15
- applyMiddleware(...createMiddlewares(session, options)),
24
+ applyMiddleware(
25
+ ...createMiddlewares(session, getCreateMiddlewaresOptions(options)),
26
+ ),
16
27
  );
17
28
 
18
29
  // Create `ScrollPositionRestoration`.
@@ -78,7 +89,12 @@ export default class NavigationStack {
78
89
 
79
90
  locationRendered() {
80
91
  if (this._scrollPositionRestoration) {
81
- this._scrollPositionRestoration.locationRendered(this.current());
92
+ const location = this.current();
93
+ if (!location) {
94
+ throw new Error('Not initialized');
95
+ }
96
+ return this._scrollPositionRestoration.locationRendered(location);
82
97
  }
98
+ return Promise.resolve();
83
99
  }
84
100
  }
package/src/debug.js ADDED
@@ -0,0 +1,8 @@
1
+ const DEBUG = false;
2
+
3
+ export default function debug(...args) {
4
+ if (DEBUG) {
5
+ // eslint-disable-next-line no-console
6
+ console.log(...args);
7
+ }
8
+ }
@@ -1,7 +1,7 @@
1
1
  // Converts `LocationInternal` object to a publicly-visible `Location` object.
2
- // It hides non-essential properties of location such as `operation`, `index`, `delta`.
2
+ // It hides non-essential properties of location such as `operation` and `delta`.
3
3
  export default function getLocationFromInternalLocation(internalLocation) {
4
4
  // eslint-disable-next-line no-unused-vars
5
- const { operation, index, delta, ...location } = internalLocation;
5
+ const { operation, delta, ...location } = internalLocation;
6
6
  return location;
7
7
  }
@@ -1,5 +1,6 @@
1
1
  /* eslint-disable no-underscore-dangle */
2
2
 
3
+ import debug from './debug';
3
4
  import isPromise from './isPromise';
4
5
 
5
6
  export function getNavigationBlockers(session) {
@@ -86,6 +87,7 @@ export function runNavigationBlockers(navigationBlockers, toLocation) {
86
87
  if (isPromise(result)) {
87
88
  return result.then((resultValue) => {
88
89
  if (resultValue) {
90
+ debug('Navigation blocked', toLocation.pathname);
89
91
  return resultValue;
90
92
  }
91
93
  return next();
@@ -93,6 +95,7 @@ export function runNavigationBlockers(navigationBlockers, toLocation) {
93
95
  }
94
96
 
95
97
  if (result) {
98
+ debug('Navigation blocked', toLocation.pathname);
96
99
  return result;
97
100
  }
98
101
  return next();
@@ -2,6 +2,7 @@
2
2
 
3
3
  import { PAGE_SCROLLABLE_CONTAINER_KEY } from './constants';
4
4
  import scheduleNextTick from './scheduleNextTick';
5
+ import debug from '../debug';
5
6
 
6
7
  export default class ScrollPositionAutoSaver {
7
8
  constructor({
@@ -67,17 +68,26 @@ export default class ScrollPositionAutoSaver {
67
68
  }
68
69
  }
69
70
 
70
- cancelSavePageScrollPosition() {
71
+ cancelSavePageScrollPosition(hasRun) {
71
72
  if (this._cancelSavePageScrollPosition) {
73
+ if (!hasRun) {
74
+ debug(
75
+ 'cancel delayed save scroll position',
76
+ PAGE_SCROLLABLE_CONTAINER_KEY,
77
+ );
78
+ }
72
79
  this._cancelSavePageScrollPosition();
73
80
  this._cancelSavePageScrollPosition = null;
74
81
  }
75
82
  }
76
83
 
77
- cancelSaveScrollableContainerScrollPosition(scrollableContainerKey) {
84
+ cancelSaveScrollableContainerScrollPosition(scrollableContainerKey, hasRun) {
78
85
  const scrollableContainerEntry =
79
86
  this._getScrollableContainers()[scrollableContainerKey];
80
87
  if (scrollableContainerEntry.cancelSaveScrollPosition) {
88
+ if (!hasRun) {
89
+ debug('cancel delayed save scroll position', scrollableContainerKey);
90
+ }
81
91
  scrollableContainerEntry.cancelSaveScrollPosition();
82
92
  scrollableContainerEntry.cancelSaveScrollPosition = null;
83
93
  }
@@ -117,8 +127,13 @@ export default class ScrollPositionAutoSaver {
117
127
  // because there might be too many in a given short period of time
118
128
  // which could affect the performance of the application.
119
129
  if (!scrollableContainerEntry.cancelSaveScrollPosition) {
130
+ debug('scroll detected', scrollableContainerKey);
120
131
  scrollableContainerEntry.cancelSaveScrollPosition =
121
132
  scheduleNextTick(() => {
133
+ debug(
134
+ 'auto-save scroll position after scroll',
135
+ scrollableContainerKey,
136
+ );
122
137
  this._scrollPositionSaver.saveScrollableContainerScrollPosition(
123
138
  scrollableContainerKey,
124
139
  scrollableContainerEntry.scrollableContainer,
@@ -133,6 +148,8 @@ export default class ScrollPositionAutoSaver {
133
148
  // Set up scroll listener on the page.
134
149
  this._removePageScrollListener =
135
150
  this._scrollPosition.addPageScrollListener(() => {
151
+ debug('scroll detected', PAGE_SCROLLABLE_CONTAINER_KEY);
152
+
136
153
  // This flag is not used in real life and is only used in tests (for some reason).
137
154
  if (!this._shouldSaveScrollPosition()) {
138
155
  return;