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.
- package/CHANGELOG.md +12 -0
- package/README.md +20 -12
- package/data-storage/package.json +2 -1
- package/lib/cjs/NavigationStack.js +17 -2
- package/lib/cjs/debug.js +12 -0
- package/lib/cjs/getLocationFromInternalLocation.js +2 -2
- package/lib/cjs/navigationBlockers.js +3 -0
- package/lib/cjs/scroll-position/ScrollPositionAutoSaver.js +13 -2
- package/lib/cjs/scroll-position/ScrollPositionRestoration.js +37 -13
- package/lib/cjs/scroll-position/ScrollPositionSaver.js +8 -2
- package/lib/cjs/session/Session.js +6 -0
- package/lib/cjs/session/navigation/operation/operations.js +4 -4
- package/lib/esm/NavigationStack.js +17 -2
- package/lib/esm/debug.js +7 -0
- package/lib/esm/getLocationFromInternalLocation.js +2 -2
- package/lib/esm/navigationBlockers.js +3 -0
- package/lib/esm/scroll-position/ScrollPositionAutoSaver.js +13 -2
- package/lib/esm/scroll-position/ScrollPositionRestoration.js +37 -13
- package/lib/esm/scroll-position/ScrollPositionSaver.js +8 -2
- package/lib/esm/session/Session.js +6 -0
- package/lib/esm/session/navigation/operation/operations.js +4 -4
- package/lib/index.d.ts +10 -9
- package/lib/scroll-position/index.d.ts +11 -11
- package/package.json +1 -1
- package/redux/package.json +2 -1
- package/scroll-position/package.json +2 -1
- package/src/NavigationStack.js +18 -2
- package/src/debug.js +8 -0
- package/src/getLocationFromInternalLocation.js +2 -2
- package/src/navigationBlockers.js +3 -0
- package/src/scroll-position/ScrollPositionAutoSaver.js +19 -2
- package/src/scroll-position/ScrollPositionRestoration.js +100 -53
- package/src/scroll-position/ScrollPositionSaver.js +21 -1
- package/src/session/Session.js +22 -0
- package/src/session/navigation/operation/operations.js +4 -4
- package/test/NavigationStack.test.js +130 -27
- package/test/middlewareTestUtil.js +1 -1
- package/test/redux/locationReducer.test.js +1 -1
- package/test/redux/middleware/createNonProgrammaticNavigationBlockerMiddleware.test.js +5 -5
- package/test/redux/middleware/createProgrammaticNavigationBlockerMiddleware.test.js +2 -2
- package/test/redux/middleware/navigationOperationMiddleware.test.js +2 -2
- package/test/scroll-position/ScrollPositionRestoration.test.js +78 -61
- package/test/scroll-position/addScrollableContainer.js +5 -2
- package/test/scroll-position/{addScrollableContainerWithHyperlink.js → addScrollableContainerWithAnchors.js} +8 -2
- package/test/scroll-position/createApp.js +28 -7
- package/test/scroll-position/withScrollableContainerAtIndexPageWithDisabledAutomaticScrollPositionRestoration.js +72 -0
- package/test/session/InMemorySession.test.js +28 -28
- package/test/session/ServerSession.test.js +1 -1
- package/test/session/WebBrowserSession.test.js +17 -17
- package/types/index.d.ts +10 -9
- package/types/scroll-position/index.d.ts +11 -11
- package/test/scroll-position/withScrollableContainerAtIndexPage.js +0 -62
|
@@ -40,7 +40,7 @@ describe('createNonProgrammaticNavigationBlockerMiddleware', () => {
|
|
|
40
40
|
// sandbox.restore();
|
|
41
41
|
});
|
|
42
42
|
|
|
43
|
-
describe('
|
|
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: '
|
|
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: '
|
|
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: '
|
|
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: '
|
|
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('
|
|
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: '
|
|
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: '
|
|
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: '
|
|
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
|
|
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
|
|
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 =
|
|
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
|
|
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 =
|
|
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 =
|
|
134
|
+
const app = addScrollableContainerWithAnchors(
|
|
134
135
|
createApp({
|
|
135
|
-
|
|
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 =
|
|
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 =
|
|
209
|
+
const app = addScrollableContainerWithAnchors(
|
|
209
210
|
createApp({
|
|
210
|
-
|
|
211
|
+
getSavedPageScrollPositionOnLocationChange: () => [10, 20],
|
|
211
212
|
}),
|
|
212
213
|
);
|
|
213
214
|
|
|
@@ -230,54 +231,66 @@ describe('ScrollPositionRestoration', () => {
|
|
|
230
231
|
]);
|
|
231
232
|
});
|
|
232
233
|
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
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
|
-
|
|
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
|
|
312
|
-
const { container, ...app } =
|
|
313
|
-
|
|
314
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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(
|
|
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
|
|
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
|
-
|
|
9
|
-
|
|
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
|
-
|
|
52
|
-
|
|
53
|
-
|
|
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
|
-
|
|
79
|
-
options && options.
|
|
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
|
+
}
|