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
@@ -13,7 +13,7 @@ describe('InMemorySession', () => {
13
13
  session.start(parseInputLocation('/initial?bar=baz#qux'));
14
14
 
15
15
  expect(location).to.deep.include({
16
- operation: 'INIT',
16
+ operation: 'init',
17
17
  pathname: '/initial',
18
18
  search: '?bar=baz',
19
19
  query: {
@@ -42,21 +42,21 @@ describe('InMemorySession', () => {
42
42
 
43
43
  expect(listener).to.have.been.calledOnce();
44
44
  expect(listener.firstCall.args[0]).to.deep.include({
45
- operation: 'INIT',
45
+ operation: 'init',
46
46
  pathname: '/initial',
47
47
  index: 0,
48
48
  delta: 0,
49
49
  });
50
50
  listener.resetHistory();
51
51
 
52
- session.navigate('PUSH', {
52
+ session.navigate('push', {
53
53
  pathname: '/new',
54
54
  });
55
55
 
56
56
  const newLocation = location;
57
57
 
58
58
  expect(newLocation).to.deep.include({
59
- operation: 'PUSH',
59
+ operation: 'push',
60
60
  pathname: '/new',
61
61
  index: 1,
62
62
  delta: 1,
@@ -65,17 +65,17 @@ describe('InMemorySession', () => {
65
65
 
66
66
  expect(listener).to.have.been.calledOnce();
67
67
  expect(listener.firstCall.args[0]).to.deep.include({
68
- operation: 'PUSH',
68
+ operation: 'push',
69
69
  pathname: '/new',
70
70
  index: 1,
71
71
  delta: 1,
72
72
  });
73
73
  listener.resetHistory();
74
74
 
75
- session.navigate('PUSH', { pathname: '/new-2' });
75
+ session.navigate('push', { pathname: '/new-2' });
76
76
 
77
77
  expect(location).to.include({
78
- operation: 'PUSH',
78
+ operation: 'push',
79
79
  pathname: '/new-2',
80
80
  index: 2,
81
81
  delta: 1,
@@ -83,17 +83,17 @@ describe('InMemorySession', () => {
83
83
 
84
84
  expect(listener).to.have.been.calledOnce();
85
85
  expect(listener.firstCall.args[0]).to.deep.include({
86
- operation: 'PUSH',
86
+ operation: 'push',
87
87
  pathname: '/new-2',
88
88
  index: 2,
89
89
  delta: 1,
90
90
  });
91
91
  listener.resetHistory();
92
92
 
93
- session.navigate('REPLACE', { pathname: '/new-3' });
93
+ session.navigate('replace', { pathname: '/new-3' });
94
94
 
95
95
  expect(location).to.include({
96
- operation: 'REPLACE',
96
+ operation: 'replace',
97
97
  pathname: '/new-3',
98
98
  index: 2,
99
99
  delta: 0,
@@ -101,7 +101,7 @@ describe('InMemorySession', () => {
101
101
 
102
102
  expect(listener).to.have.been.calledOnce();
103
103
  expect(listener.firstCall.args[0]).to.deep.include({
104
- operation: 'REPLACE',
104
+ operation: 'replace',
105
105
  pathname: '/new-3',
106
106
  index: 2,
107
107
  delta: 0,
@@ -112,7 +112,7 @@ describe('InMemorySession', () => {
112
112
 
113
113
  expect(listener).to.have.been.calledOnce();
114
114
  expect(listener.firstCall.args[0]).to.deep.include({
115
- operation: 'SHIFT',
115
+ operation: 'shift',
116
116
  pathname: '/new',
117
117
  key: newLocation.key,
118
118
  index: 1,
@@ -126,8 +126,8 @@ describe('InMemorySession', () => {
126
126
  it('should support subscribing and unsubscribing', () => {
127
127
  const session = new InMemorySession();
128
128
  session.start(parseInputLocation('/initial'));
129
- session.navigate('PUSH', { pathname: '/new' });
130
- session.navigate('PUSH', { pathname: '/new-2' });
129
+ session.navigate('push', { pathname: '/new' });
130
+ session.navigate('push', { pathname: '/new-2' });
131
131
 
132
132
  const listener = sinon.spy();
133
133
  const unsubscribe = session.subscribe(listener);
@@ -136,7 +136,7 @@ describe('InMemorySession', () => {
136
136
 
137
137
  expect(listener).to.have.been.calledOnce();
138
138
  expect(listener.firstCall.args[0]).to.include({
139
- operation: 'SHIFT',
139
+ operation: 'shift',
140
140
  pathname: '/new',
141
141
  });
142
142
  listener.resetHistory();
@@ -153,8 +153,8 @@ describe('InMemorySession', () => {
153
153
  it('should respect stack bounds', () => {
154
154
  const session = new InMemorySession();
155
155
  session.start(parseInputLocation('/initial'));
156
- session.navigate('PUSH', { pathname: '/new' });
157
- session.navigate('PUSH', { pathname: '/new-2' });
156
+ session.navigate('push', { pathname: '/new' });
157
+ session.navigate('push', { pathname: '/new-2' });
158
158
 
159
159
  const listener = sinon.spy();
160
160
  session.subscribe(listener);
@@ -169,7 +169,7 @@ describe('InMemorySession', () => {
169
169
 
170
170
  expect(listener).to.have.been.calledOnce();
171
171
  expect(listener.firstCall.args[0]).to.include({
172
- operation: 'SHIFT',
172
+ operation: 'shift',
173
173
  pathname: '/initial',
174
174
  delta: -2,
175
175
  });
@@ -189,7 +189,7 @@ describe('InMemorySession', () => {
189
189
 
190
190
  expect(listener).to.have.been.calledOnce();
191
191
  expect(listener.firstCall.args[0]).to.include({
192
- operation: 'SHIFT',
192
+ operation: 'shift',
193
193
  pathname: '/new-2',
194
194
  delta: 2,
195
195
  });
@@ -207,10 +207,10 @@ describe('InMemorySession', () => {
207
207
  it('should not reset forward entries on replace', () => {
208
208
  const session = new InMemorySession();
209
209
  session.start(parseInputLocation('/initial'));
210
- session.navigate('PUSH', { pathname: '/new' });
211
- session.navigate('PUSH', { pathname: '/new-2' });
210
+ session.navigate('push', { pathname: '/new' });
211
+ session.navigate('push', { pathname: '/new-2' });
212
212
  session.shift(-2);
213
- session.navigate('REPLACE', { pathname: '/new-3' });
213
+ session.navigate('replace', { pathname: '/new-3' });
214
214
 
215
215
  const listener = sinon.spy();
216
216
  session.subscribe(listener);
@@ -219,7 +219,7 @@ describe('InMemorySession', () => {
219
219
 
220
220
  expect(listener).to.have.been.calledOnce();
221
221
  expect(listener.firstCall.args[0]).to.include({
222
- operation: 'SHIFT',
222
+ operation: 'shift',
223
223
  pathname: '/new',
224
224
  delta: 1,
225
225
  });
@@ -231,10 +231,10 @@ describe('InMemorySession', () => {
231
231
  const session = new InMemorySession();
232
232
 
233
233
  session.start(parseInputLocation('/initial'));
234
- session.navigate('PUSH', { pathname: '/new' });
235
- session.navigate('PUSH', { pathname: '/new-2' });
234
+ session.navigate('push', { pathname: '/new' });
235
+ session.navigate('push', { pathname: '/new-2' });
236
236
  session.shift(-2);
237
- session.navigate('PUSH', { pathname: '/new-3' });
237
+ session.navigate('push', { pathname: '/new-3' });
238
238
 
239
239
  const listener = sinon.spy();
240
240
  session.subscribe(listener);
@@ -297,8 +297,8 @@ describe('InMemorySession', () => {
297
297
  // pathname: '/initial',
298
298
  // });
299
299
  //
300
- // session1._navigation.navigate('PUSH', { pathname: '/new' });
301
- // session1._navigation.navigate('PUSH', { pathname: '/new-2' });
300
+ // session1._navigation.navigate('push', { pathname: '/new' });
301
+ // session1._navigation.navigate('push', { pathname: '/new-2' });
302
302
  // session1._navigation.shift(-1);
303
303
  //
304
304
  // const session2 = new InMemorySession({ save, load });
@@ -13,7 +13,7 @@ describe('ServerSideRenderSession', () => {
13
13
  session.start(parseInputLocation('/foo?bar=baz#qux'));
14
14
 
15
15
  expect(location).to.deep.include({
16
- operation: 'INIT',
16
+ operation: 'init',
17
17
  pathname: '/foo',
18
18
  search: '?bar=baz',
19
19
  query: {
@@ -62,7 +62,7 @@ describe('WebBrowserSession', () => {
62
62
  session.start(parseInputLocation(window.location));
63
63
 
64
64
  expect(location).to.deep.include({
65
- operation: 'INIT',
65
+ operation: 'init',
66
66
  pathname: '/initial',
67
67
  search: '?bar=baz',
68
68
  query: {
@@ -78,7 +78,7 @@ describe('WebBrowserSession', () => {
78
78
  session = new WebBrowserSession();
79
79
 
80
80
  expect(() =>
81
- session.navigate('PUSH', {
81
+ session.navigate('push', {
82
82
  pathname: '/new',
83
83
  search: '?search',
84
84
  hash: '#hash',
@@ -103,14 +103,14 @@ describe('WebBrowserSession', () => {
103
103
 
104
104
  expect(listener).to.have.been.calledOnce();
105
105
  expect(listener.firstCall.args[0]).to.deep.include({
106
- operation: 'INIT',
106
+ operation: 'init',
107
107
  pathname: '/initial',
108
108
  index: 0,
109
109
  delta: 0,
110
110
  });
111
111
  listener.resetHistory();
112
112
 
113
- session.navigate('PUSH', {
113
+ session.navigate('push', {
114
114
  pathname: '/new',
115
115
  search: '?search',
116
116
  hash: '#hash',
@@ -124,7 +124,7 @@ describe('WebBrowserSession', () => {
124
124
  hash: '#hash',
125
125
  });
126
126
  expect(newLocation).to.deep.include({
127
- operation: 'PUSH',
127
+ operation: 'push',
128
128
  pathname: '/new',
129
129
  search: '?search',
130
130
  hash: '#hash',
@@ -135,21 +135,21 @@ describe('WebBrowserSession', () => {
135
135
 
136
136
  expect(listener).to.have.been.calledOnce();
137
137
  expect(listener.firstCall.args[0]).to.deep.include({
138
- operation: 'PUSH',
138
+ operation: 'push',
139
139
  pathname: '/new',
140
140
  index: 1,
141
141
  delta: 1,
142
142
  });
143
143
  listener.resetHistory();
144
144
 
145
- session.navigate('PUSH', {
145
+ session.navigate('push', {
146
146
  pathname: '/new-2',
147
147
  search: '',
148
148
  hash: '',
149
149
  });
150
150
 
151
151
  expect(location).to.include({
152
- operation: 'PUSH',
152
+ operation: 'push',
153
153
  pathname: '/new-2',
154
154
  index: 2,
155
155
  delta: 1,
@@ -157,7 +157,7 @@ describe('WebBrowserSession', () => {
157
157
 
158
158
  expect(listener).to.have.been.calledOnce();
159
159
  expect(listener.firstCall.args[0]).to.deep.include({
160
- operation: 'PUSH',
160
+ operation: 'push',
161
161
  pathname: '/new-2',
162
162
  index: 2,
163
163
  delta: 1,
@@ -166,14 +166,14 @@ describe('WebBrowserSession', () => {
166
166
 
167
167
  expect(window.location.pathname).to.equal('/new-2');
168
168
 
169
- session.navigate('REPLACE', {
169
+ session.navigate('replace', {
170
170
  pathname: '/new-3',
171
171
  search: '',
172
172
  hash: '',
173
173
  });
174
174
 
175
175
  expect(location).to.include({
176
- operation: 'REPLACE',
176
+ operation: 'replace',
177
177
  pathname: '/new-3',
178
178
  index: 2,
179
179
  delta: 0,
@@ -184,7 +184,7 @@ describe('WebBrowserSession', () => {
184
184
 
185
185
  expect(listener).to.have.been.calledOnce();
186
186
  expect(listener.firstCall.args[0]).to.deep.include({
187
- operation: 'REPLACE',
187
+ operation: 'replace',
188
188
  pathname: '/new-3',
189
189
  index: 2,
190
190
  delta: 0,
@@ -202,7 +202,7 @@ describe('WebBrowserSession', () => {
202
202
 
203
203
  expect(listener).to.have.been.calledOnce();
204
204
  expect(listener.firstCall.args[0]).to.deep.include({
205
- operation: 'SHIFT',
205
+ operation: 'shift',
206
206
  pathname: '/new',
207
207
  search: '?search',
208
208
  hash: '#hash',
@@ -219,7 +219,7 @@ describe('WebBrowserSession', () => {
219
219
 
220
220
  expect(listener).to.have.been.calledOnce();
221
221
  expect(listener.firstCall.args[0]).to.deep.include({
222
- operation: 'SHIFT',
222
+ operation: 'shift',
223
223
  pathname: '/initial',
224
224
  index: 0,
225
225
  delta: -1,
@@ -231,12 +231,12 @@ describe('WebBrowserSession', () => {
231
231
  window.history.replaceState(null, null, '/');
232
232
  session = new WebBrowserSession();
233
233
  session.start(parseInputLocation(window.location));
234
- session.navigate('PUSH', {
234
+ session.navigate('push', {
235
235
  pathname: '/new',
236
236
  search: '',
237
237
  hash: '',
238
238
  });
239
- session.navigate('PUSH', {
239
+ session.navigate('push', {
240
240
  pathname: '/new-2',
241
241
  search: '',
242
242
  hash: '',
@@ -250,7 +250,7 @@ describe('WebBrowserSession', () => {
250
250
 
251
251
  expect(listener).to.have.been.calledOnce();
252
252
  expect(listener.firstCall.args[0]).to.include({
253
- operation: 'SHIFT',
253
+ operation: 'shift',
254
254
  pathname: '/new',
255
255
  });
256
256
  listener.resetHistory();
package/types/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
 
@@ -1,62 +0,0 @@
1
- // Adds a 100x100 scrollable container on the website.
2
- //
3
- // * When the current location is "/", it renders a large amount of content
4
- // (20000x20000 to be specific) inside the container, making it scrollable.
5
- //
6
- // * At any other location, it doesn't render anything in the container.
7
- //
8
- export default function withScrollableContainerAtIndexPage(app) {
9
- const container = document.createElement('div');
10
- container.style.height = '100px';
11
- container.style.width = '100px';
12
- container.style.overflow = 'hidden';
13
- document.body.appendChild(container);
14
-
15
- let element;
16
- let unregister;
17
-
18
- // This will only be called once, so no need to guard.
19
- function listen(listener) {
20
- function shouldUpdatePageScrollPositionForLocation(
21
- location,
22
- prevLocation,
23
- ) {
24
- // Disable the automatic scroll restoration after a SHIFT, to check the
25
- // scroll-on-register behavior.
26
- if (prevLocation && location.operation === 'SHIFT') {
27
- return false;
28
- }
29
-
30
- return true;
31
- }
32
-
33
- const unlisten = app.listen((location) => {
34
- listener(location);
35
-
36
- if (location.pathname === '/') {
37
- element = document.createElement('div');
38
- element.style.height = '20000px';
39
- element.style.width = '20000px';
40
- container.appendChild(element);
41
-
42
- unregister = app.registerScrollableContainer('container', container, {
43
- shouldUpdatePageScrollPositionForLocation,
44
- });
45
- } else {
46
- container.removeChild(element);
47
- unregister();
48
- }
49
- });
50
-
51
- return () => {
52
- unlisten();
53
- document.body.removeChild(container);
54
- };
55
- }
56
-
57
- return {
58
- ...app,
59
- container,
60
- listen,
61
- };
62
- }