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
@@ -3,7 +3,7 @@ import locationReducer from '../../src/redux/locationReducer';
3
3
 
4
4
  describe('locationReducer', () => {
5
5
  const prevState = {
6
- operation: 'PUSH',
6
+ operation: 'push',
7
7
  delta: 1,
8
8
  hash: '',
9
9
  index: 5,
@@ -40,7 +40,7 @@ describe('createNonProgrammaticNavigationBlockerMiddleware', () => {
40
40
  // sandbox.restore();
41
41
  });
42
42
 
43
- describe('SHIFT navigation', () => {
43
+ describe('shift navigation', () => {
44
44
  beforeEach(() => {
45
45
  store.dispatch(Actions.push('/new'));
46
46
  });
@@ -53,7 +53,7 @@ describe('createNonProgrammaticNavigationBlockerMiddleware', () => {
53
53
  expect(store.getState().pathname).to.equal('/initial');
54
54
 
55
55
  expect(blocker.firstCall.args[0]).to.include({
56
- // operation: 'SHIFT',
56
+ // operation: 'shift',
57
57
  pathname: '/initial',
58
58
  // delta: -1,
59
59
  });
@@ -199,7 +199,7 @@ describe('createNonProgrammaticNavigationBlockerMiddleware', () => {
199
199
  // session._currentLocationIndex = 0;
200
200
  // session._navigation._triggerUpdateInternalLocationMiddlewareListener(
201
201
  // session._navigation._createLocationObject({
202
- // operation: 'SHIFT',
202
+ // operation: 'shift',
203
203
  // delta: null,
204
204
  // }),
205
205
  // );
@@ -222,7 +222,7 @@ describe('createNonProgrammaticNavigationBlockerMiddleware', () => {
222
222
  // /* eslint-disable no-underscore-dangle */
223
223
  // session._navigation._index = 0;
224
224
  // session._navigation._subscriptionListener(session._navigation._createLocationObject({
225
- // operation: 'SHIFT',
225
+ // operation: 'shift',
226
226
  // delta: null,
227
227
  // }));
228
228
  // /* eslint-enable no-underscore-dangle */
@@ -245,7 +245,7 @@ describe('createNonProgrammaticNavigationBlockerMiddleware', () => {
245
245
  // /* eslint-disable no-underscore-dangle */
246
246
  // session._navigation._index = 0;
247
247
  // session._navigation._subscriptionListener(session._navigation._createLocationObject({
248
- // operation: 'SHIFT',
248
+ // operation: 'shift',
249
249
  // delta: null,
250
250
  // }));
251
251
  // /* eslint-enable no-underscore-dangle */
@@ -41,7 +41,7 @@ describe('createProgrammaticNavigationBlockerMiddleware', () => {
41
41
  // sandbox.restore();
42
42
  });
43
43
 
44
- describe('PUSH navigation', () => {
44
+ describe('push navigation', () => {
45
45
  it('should block navigation when blocker returns `true`', () => {
46
46
  const blocker = sinon.stub().returns(true);
47
47
  addNavigationBlocker(blocker);
@@ -52,7 +52,7 @@ describe('createProgrammaticNavigationBlockerMiddleware', () => {
52
52
  expect(blocker).to.have.been.calledOnce();
53
53
 
54
54
  expect(blocker.firstCall.args[0]).to.include({
55
- // operation: 'PUSH',
55
+ // operation: 'push',
56
56
  pathname: '/new',
57
57
  });
58
58
  });
@@ -22,7 +22,7 @@ describe('navigationOperationMiddleware', () => {
22
22
  expect(next).to.be.calledWith({
23
23
  type: ActionTypes.NAVIGATE,
24
24
  payload: {
25
- operation: 'PUSH',
25
+ operation: 'push',
26
26
  location: {
27
27
  pathname: '/foo',
28
28
  search: '?bar=baz',
@@ -45,7 +45,7 @@ describe('navigationOperationMiddleware', () => {
45
45
  expect(next).to.be.calledWith({
46
46
  type: ActionTypes.NAVIGATE,
47
47
  payload: {
48
- operation: 'REPLACE',
48
+ operation: 'replace',
49
49
  location: {
50
50
  pathname: '/foo',
51
51
  search: '?bar=baz',
@@ -2,12 +2,12 @@ import { offset, scrollLeft, scrollTop } from 'dom-helpers';
2
2
  import sinon from 'sinon';
3
3
 
4
4
  import addScrollableContainer from './addScrollableContainer';
5
- import addScrollableContainerWithHyperlink from './addScrollableContainerWithHyperlink';
5
+ import addScrollableContainerWithAnchors from './addScrollableContainerWithAnchors';
6
6
  import createApp from './createApp';
7
7
  import delay from './delay';
8
8
  import { setEventListener, triggerEvent } from './mockPageLifecycle';
9
9
  import runApp from './runApp';
10
- import withScrollableContainerAtIndexPage from './withScrollableContainerAtIndexPage';
10
+ import withScrollableContainerAtIndexPageWithDisabledAutomaticScrollPositionRestoration from './withScrollableContainerAtIndexPageWithDisabledAutomaticScrollPositionRestoration';
11
11
  import PageLifecycle from '../../src/session/lifecycle/page-lifecycle/PageLifecycleInstance';
12
12
 
13
13
  describe('ScrollPositionRestoration', () => {
@@ -59,13 +59,14 @@ describe('ScrollPositionRestoration', () => {
59
59
 
60
60
  describe('default behavior', () => {
61
61
  it('should emulate browser scroll behavior', (done) => {
62
- const app = addScrollableContainerWithHyperlink(createApp());
62
+ const app = addScrollableContainerWithAnchors(createApp());
63
63
  const child1 = document.getElementById('child1');
64
64
  const child2 = document.getElementById('child2-id');
65
65
 
66
66
  unlisten = runApp(app, [
67
67
  () => {
68
- // This will be ignored, but will exercise the throttle logic.
68
+ // This scroll will be ignored (overwritten by a subsequent scroll),
69
+ // but it will test the "throttle scroll events" code.
69
70
  scrollTop(window, 10000);
70
71
 
71
72
  setTimeout(() => {
@@ -113,7 +114,7 @@ describe('ScrollPositionRestoration', () => {
113
114
  configurable: true,
114
115
  });
115
116
 
116
- const app = addScrollableContainerWithHyperlink(createApp());
117
+ const app = addScrollableContainerWithAnchors(createApp());
117
118
 
118
119
  unlisten = runApp(app, [
119
120
  () => {
@@ -130,9 +131,9 @@ describe('ScrollPositionRestoration', () => {
130
131
 
131
132
  describe('custom behavior', () => {
132
133
  it('should allow scroll suppression', (done) => {
133
- const app = addScrollableContainerWithHyperlink(
134
+ const app = addScrollableContainerWithAnchors(
134
135
  createApp({
135
- shouldUpdatePageScrollPositionForLocation: (
136
+ shouldSetPageScrollPositionOnLocationChange: (
136
137
  location,
137
138
  prevLocation,
138
139
  ) => {
@@ -167,7 +168,7 @@ describe('ScrollPositionRestoration', () => {
167
168
  });
168
169
 
169
170
  it('should ignore scroll events when `disableSavingScrollPosition()` is used', (done) => {
170
- const app = addScrollableContainerWithHyperlink(createApp());
171
+ const app = addScrollableContainerWithAnchors(createApp());
171
172
 
172
173
  unlisten = runApp(app, [
173
174
  () => {
@@ -205,9 +206,9 @@ describe('ScrollPositionRestoration', () => {
205
206
  });
206
207
 
207
208
  it('should allow custom position', (done) => {
208
- const app = addScrollableContainerWithHyperlink(
209
+ const app = addScrollableContainerWithAnchors(
209
210
  createApp({
210
- getPageScrollPositionForLocation: () => [10, 20],
211
+ getSavedPageScrollPositionOnLocationChange: () => [10, 20],
211
212
  }),
212
213
  );
213
214
 
@@ -230,54 +231,66 @@ describe('ScrollPositionRestoration', () => {
230
231
  ]);
231
232
  });
232
233
 
233
- it('should save scroll position even if no scroll events are dispatched', (done) => {
234
- let customInitialPageScrollPosition;
235
-
236
- const app = addScrollableContainerWithHyperlink(
237
- createApp({
238
- // eslint-disable-next-line no-unused-vars
239
- getPageScrollPositionForLocation(location, prevLocation) {
240
- if (customInitialPageScrollPosition) {
241
- return [10, 20];
242
- }
243
- return undefined;
244
- },
245
- }),
246
- );
247
-
248
- unlisten = runApp(app, [
249
- () => {
250
- app.goTo('/detail');
251
- },
252
- () => {
253
- customInitialPageScrollPosition = [10, 20];
254
- app.goTo('/');
255
- },
256
- () => {
257
- customInitialPageScrollPosition = undefined;
258
- app.goTo('/detail');
259
- },
260
- () => {
261
- app.goBack();
262
- },
263
- () => {
264
- // Here, it said "expected 9.966666221618652 to equal 10".
265
- // Using `.to.be.closeTo()` here instead of `.to.equal()` to work around this browser issue.
266
- expect(scrollLeft(window)).to.be.closeTo(10, 0.5);
267
- // Here, it said "expected 19.933332443237305 to equal 20".
268
- // Using `.to.be.closeTo()` here instead of `.to.equal()` to work around this browser issue.
269
- expect(scrollTop(window)).to.be.closeTo(20, 0.5);
270
- done();
271
- },
272
- ]);
273
- });
234
+ // This test case was disabled because `ScrollPositionRestoration` doesn't save
235
+ // scroll position on page load. It only saves scroll position on actual scroll events.
236
+ // If there were no scroll events, the scroll position doesn't get saved.
237
+ // The rationale is that when there were no scroll events, the scroll position
238
+ // is gonna be either a default one or a custom one specified by passing a custom
239
+ // `getSavedPageScrollPositionOnLocationChange()` function. In the latter case, the custom
240
+ // `getSavedPageScrollPositionOnLocationChange()` function is responsible to return a correct scroll position
241
+ // every time it gets called rather than just return a correct scroll position once,
242
+ // save it immediately and then restore it when returning to the page.
243
+ //
244
+ // it('should save scroll position even if no scroll events are dispatched', (done) => {
245
+ // let customInitialPageScrollPosition;
246
+ //
247
+ // const app = addScrollableContainerWithAnchors(
248
+ // createApp({
249
+ // // eslint-disable-next-line no-unused-vars
250
+ // getSavedPageScrollPositionOnLocationChange(location, prevLocation) {
251
+ // // Only when navigated via `.goTo()`. Ignore `.goBack()` navigation.
252
+ // if (prevLocation && location.index > prevLocation.index) {
253
+ // return [10, 20];
254
+ // }
255
+ // return undefined;
256
+ // },
257
+ // }),
258
+ // );
259
+ //
260
+ // unlisten = runApp(app, [
261
+ // () => {
262
+ // app.goTo('/detail');
263
+ // },
264
+ // () => {
265
+ // app.goTo('/');
266
+ // },
267
+ // () => {
268
+ // app.goTo('/detail');
269
+ // },
270
+ // () => {
271
+ // if (customInitialPageScrollPosition === 123) {
272
+ // customInitialPageScrollPosition = 456;
273
+ // }
274
+ // app.goBack();
275
+ // },
276
+ // () => {
277
+ // // Here, it said "expected 9.966666221618652 to equal 10".
278
+ // // Using `.to.be.closeTo()` here instead of `.to.equal()` to work around this browser issue.
279
+ // expect(scrollLeft(window)).to.be.closeTo(10, 0.5);
280
+ // // Here, it said "expected 19.933332443237305 to equal 20".
281
+ // // Using `.to.be.closeTo()` here instead of `.to.equal()` to work around this browser issue.
282
+ // expect(scrollTop(window)).to.be.closeTo(20, 0.5);
283
+ // done();
284
+ // },
285
+ // ]);
286
+ // });
274
287
  });
275
288
 
276
289
  describe('scrollable container', () => {
277
290
  it('should follow browser scroll behavior', (done) => {
278
291
  const { container, ...app } = addScrollableContainer(
279
292
  createApp({
280
- shouldUpdatePageScrollPositionForLocation: () => false,
293
+ shouldSetPageScrollPositionOnLocationChange: () => false,
281
294
  }),
282
295
  );
283
296
 
@@ -308,12 +321,13 @@ describe('ScrollPositionRestoration', () => {
308
321
  ]);
309
322
  });
310
323
 
311
- it('should restore scroll on remount', (done) => {
312
- const { container, ...app } = withScrollableContainerAtIndexPage(
313
- createApp({
314
- shouldUpdatePageScrollPositionForLocation: () => false,
315
- }),
316
- );
324
+ it('should automatically restore a previously-saved scroll position when adding a scrollable container', (done) => {
325
+ const { container, ...app } =
326
+ withScrollableContainerAtIndexPageWithDisabledAutomaticScrollPositionRestoration(
327
+ createApp({
328
+ shouldSetPageScrollPositionOnLocationChange: () => false,
329
+ }),
330
+ );
317
331
 
318
332
  unlisten = runApp(app, [
319
333
  () => {
@@ -325,6 +339,8 @@ describe('ScrollPositionRestoration', () => {
325
339
  () => {
326
340
  expect(container.scrollHeight).to.equal(100);
327
341
  expect(scrollTop(container)).to.equal(0);
342
+ // This `scrollTop()` won't trigger a "scroll" event
343
+ // because the container height is only `100` so it's not scrollable.
328
344
  scrollTop(container, 5000);
329
345
  delay(() => {
330
346
  app.goBack();
@@ -343,7 +359,7 @@ describe('ScrollPositionRestoration', () => {
343
359
  it('should save element scroll position on scroll event, i.e. before navigation is even attempted', (done) => {
344
360
  const app1 = addScrollableContainer(
345
361
  createApp({
346
- shouldUpdatePageScrollPositionForLocation: () => false,
362
+ shouldSetPageScrollPositionOnLocationChange: () => false,
347
363
  }),
348
364
  );
349
365
 
@@ -357,8 +373,9 @@ describe('ScrollPositionRestoration', () => {
357
373
 
358
374
  const app2 = addScrollableContainer(
359
375
  createApp({
376
+ // Restore the data of the session of `app1`.
360
377
  sessionKey: app1.getSessionKey(),
361
- shouldUpdatePageScrollPositionForLocation: () => false,
378
+ shouldSetPageScrollPositionOnLocationChange: () => false,
362
379
  }),
363
380
  );
364
381
 
@@ -377,7 +394,7 @@ describe('ScrollPositionRestoration', () => {
377
394
 
378
395
  it('should ignore scroll events when `disableSavingScrollPosition` is used', (done) => {
379
396
  const app = addScrollableContainer(
380
- addScrollableContainerWithHyperlink(createApp()),
397
+ addScrollableContainerWithAnchors(createApp()),
381
398
  );
382
399
 
383
400
  unlisten = runApp(app, [
@@ -16,14 +16,17 @@ export default function addScrollableContainer(app) {
16
16
  // Add the scrollable container to the website.
17
17
  document.body.appendChild(container);
18
18
 
19
- // This function will only be called once, so no need to guard.
20
19
  function listen(listener) {
21
20
  const unlisten = app.listen(listener);
22
21
 
23
- app.registerScrollableContainer('container', container);
22
+ const unregisterScrollableContainer = app.registerScrollableContainer(
23
+ 'container',
24
+ container,
25
+ );
24
26
 
25
27
  return () => {
26
28
  unlisten();
29
+ unregisterScrollableContainer();
27
30
  document.body.removeChild(container);
28
31
  };
29
32
  }
@@ -7,27 +7,33 @@
7
7
  // * When the current location is "/", the scrollable container size is 20000x20000.
8
8
  // * At any other location, the scrollable container size is 10000x10000.
9
9
  //
10
- export default function withRoutes(app) {
10
+ export default function addScrollableContainerWithAnchors(app) {
11
11
  const container = document.createElement('div');
12
12
  document.body.appendChild(container);
13
13
 
14
14
  const child1 = document.createElement('div');
15
+ // In HTML, an "anchor" is matched either by `id` attribute value
16
+ // or by `name` attribute value.
15
17
  child1.id = 'child1';
16
18
  child1.style.height = '100px';
17
19
  container.appendChild(child1);
18
20
 
19
21
  const child2 = document.createElement('a');
20
22
  child2.id = 'child2-id';
23
+ // In HTML, an "anchor" is matched either by `id` attribute value
24
+ // or by `name` attribute value.
25
+ // Here, it tests the correctness of scrolling to an anchor called "child2".
26
+ // The `id` attribute value is different so that it doesn't interfere.
21
27
  child2.name = 'child2';
22
28
  child2.style.height = '100px';
23
29
  child2.appendChild(document.createTextNode('link'));
24
30
  container.appendChild(child2);
25
31
 
26
- // This function will only be called once, so no need to guard.
27
32
  function listen(listener) {
28
33
  const unlisten = app.listen((location) => {
29
34
  listener(location);
30
35
 
36
+ // Scrollable container has different height on different pages.
31
37
  if (location.pathname === '/') {
32
38
  container.style.height = '20000px';
33
39
  container.style.width = '20000px';
@@ -5,11 +5,12 @@ import WebBrowserSession from '../../src/session/WebBrowserSession';
5
5
  // Creates a website with `ScrollPositionRestoration`.
6
6
  export default function createApp({
7
7
  sessionKey,
8
- shouldUpdatePageScrollPositionForLocation,
9
- getPageScrollPositionForLocation,
8
+ shouldSetPageScrollPositionOnLocationChange,
9
+ getSavedPageScrollPositionOnLocationChange,
10
10
  } = {}) {
11
11
  let currentLocation = null;
12
12
 
13
+ let locationRenderedListeners = [];
13
14
  let listeners = [];
14
15
  let scrollPositionRestoration = null;
15
16
  let navigationStack = null;
@@ -24,6 +25,22 @@ export default function createApp({
24
25
  });
25
26
 
26
27
  scrollPositionRestoration.locationRendered(location);
28
+
29
+ for (const locationRenderedListener of locationRenderedListeners) {
30
+ locationRenderedListener(location);
31
+ }
32
+ }
33
+
34
+ // Adds a "location rendered" listener.
35
+ function whenRenderedLocation(listener) {
36
+ locationRenderedListeners.push(listener);
37
+
38
+ // Returns a "remove listener" function.
39
+ return () => {
40
+ locationRenderedListeners = locationRenderedListeners.filter(
41
+ (_) => _ !== listener,
42
+ );
43
+ };
27
44
  }
28
45
 
29
46
  // Removes a "location change" event listener.
@@ -48,9 +65,10 @@ export default function createApp({
48
65
  }
49
66
  navigationStack = new NavigationStack(session);
50
67
  scrollPositionRestoration = new ScrollPositionRestoration(session, {
51
- _shouldUpdatePageScrollPositionForLocation:
52
- shouldUpdatePageScrollPositionForLocation,
53
- _getPageScrollPositionForLocation: getPageScrollPositionForLocation,
68
+ _shouldSetPageScrollPositionOnLocationChange:
69
+ shouldSetPageScrollPositionOnLocationChange,
70
+ _getSavedPageScrollPositionOnLocationChange:
71
+ getSavedPageScrollPositionOnLocationChange,
54
72
  });
55
73
 
56
74
  unlisten = navigationStack.subscribe(onLocationDidChange);
@@ -75,8 +93,10 @@ export default function createApp({
75
93
  // Registers a scrollable container on a page.
76
94
  function registerScrollableContainer(key, element, options) {
77
95
  return scrollPositionRestoration.addScrollableContainer(key, element, {
78
- _shouldUpdateScrollPositionForLocation:
79
- options && options.shouldUpdateScrollPositionForLocation,
96
+ _shouldSetScrollPositionOnLocationChange:
97
+ options && options.shouldSetScrollPositionOnLocationChange,
98
+ _getSavedScrollPositionOnLocationChange:
99
+ options && options.getSavedScrollPositionOnLocationChange,
80
100
  });
81
101
  }
82
102
 
@@ -108,5 +128,6 @@ export default function createApp({
108
128
  disableSavingScrollPosition,
109
129
  enableSavingScrollPosition,
110
130
  getSessionKey: () => session.key,
131
+ whenRenderedLocation,
111
132
  };
112
133
  }
@@ -0,0 +1,72 @@
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 withScrollableContainerAtIndexPageWithDisabledAutomaticScrollPositionRestoration(
9
+ app,
10
+ ) {
11
+ const container = document.createElement('div');
12
+ container.style.height = '100px';
13
+ container.style.width = '100px';
14
+ container.style.overflow = 'hidden';
15
+ document.body.appendChild(container);
16
+
17
+ let scrollableContainerContentElement;
18
+ let unregisterScrollableContainer;
19
+
20
+ function listen(listener) {
21
+ function shouldUpdateScrollableContainerScrollPositionForLocation(
22
+ location,
23
+ prevLocation,
24
+ ) {
25
+ // Disable the automatic scroll position restoration on "back" navigation
26
+ // to check that it automatically restores scroll position when calling
27
+ // `ScrollPositionRestoration.addScrollableContainer()` method.
28
+ if (prevLocation && location.index < prevLocation.index) {
29
+ return false;
30
+ }
31
+ return true;
32
+ }
33
+
34
+ app.whenRenderedLocation((location) => {
35
+ if (location.pathname === '/') {
36
+ unregisterScrollableContainer = app.registerScrollableContainer(
37
+ 'container',
38
+ container,
39
+ {
40
+ shouldSetScrollPositionOnLocationChange:
41
+ shouldUpdateScrollableContainerScrollPositionForLocation,
42
+ },
43
+ );
44
+ }
45
+ });
46
+
47
+ const unlisten = app.listen((location) => {
48
+ listener(location);
49
+
50
+ if (location.pathname === '/') {
51
+ scrollableContainerContentElement = document.createElement('div');
52
+ scrollableContainerContentElement.style.height = '20000px';
53
+ scrollableContainerContentElement.style.width = '20000px';
54
+ container.appendChild(scrollableContainerContentElement);
55
+ } else {
56
+ unregisterScrollableContainer();
57
+ container.removeChild(scrollableContainerContentElement);
58
+ }
59
+ });
60
+
61
+ return () => {
62
+ unlisten();
63
+ document.body.removeChild(container);
64
+ };
65
+ }
66
+
67
+ return {
68
+ ...app,
69
+ container,
70
+ listen,
71
+ };
72
+ }