diff --git a/aio/src/app/app.component.html b/aio/src/app/app.component.html
index e929dfac86..58ebe94341 100644
--- a/aio/src/app/app.component.html
+++ b/aio/src/app/app.component.html
@@ -33,7 +33,7 @@
-
+
diff --git a/aio/src/app/app.component.spec.ts b/aio/src/app/app.component.spec.ts
index 836b180285..6f9f995a75 100644
--- a/aio/src/app/app.component.spec.ts
+++ b/aio/src/app/app.component.spec.ts
@@ -472,6 +472,53 @@ describe('AppComponent', () => {
}));
});
+ describe('restrainScrolling()', () => {
+ const preventedScrolling = (currentTarget: object, deltaY: number) => {
+ const evt = {
+ deltaY,
+ currentTarget,
+ defaultPrevented: false,
+ preventDefault() { this.defaultPrevented = true; }
+ } as any as WheelEvent;
+
+ component.restrainScrolling(evt);
+
+ return evt.defaultPrevented;
+ };
+
+ it('should prevent scrolling up if already at the top', () => {
+ const elem = {scrollTop: 0};
+
+ expect(preventedScrolling(elem, -100)).toBe(true);
+ expect(preventedScrolling(elem, +100)).toBe(false);
+ expect(preventedScrolling(elem, -10)).toBe(true);
+ });
+
+ it('should prevent scrolling down if already at the bottom', () => {
+ const elem = {scrollTop: 100, scrollHeight: 150, clientHeight: 50};
+
+ expect(preventedScrolling(elem, +10)).toBe(true);
+ expect(preventedScrolling(elem, -10)).toBe(false);
+ expect(preventedScrolling(elem, +5)).toBe(true);
+
+ elem.clientHeight -= 10;
+ expect(preventedScrolling(elem, +5)).toBe(false);
+
+ elem.scrollHeight -= 20;
+ expect(preventedScrolling(elem, +5)).toBe(true);
+
+ elem.scrollTop -= 30;
+ expect(preventedScrolling(elem, +5)).toBe(false);
+ });
+
+ it('should not prevent scrolling if neither at the top nor at the bottom', () => {
+ const elem = {scrollTop: 50, scrollHeight: 150, clientHeight: 50};
+
+ expect(preventedScrolling(elem, +100)).toBe(false);
+ expect(preventedScrolling(elem, -100)).toBe(false);
+ });
+ });
+
describe('aio-toc', () => {
let tocDebugElement: DebugElement;
let tocContainer: DebugElement;
@@ -495,6 +542,16 @@ describe('AppComponent', () => {
expect(tocContainer.styles['max-height']).toBe('100px');
});
+
+ it('should restrain scrolling inside the ToC container', () => {
+ const restrainScrolling = spyOn(component, 'restrainScrolling');
+ const evt = {};
+
+ expect(restrainScrolling).not.toHaveBeenCalled();
+
+ tocContainer.triggerEventHandler('mousewheel', evt);
+ expect(restrainScrolling).toHaveBeenCalledWith(evt);
+ });
});
describe('footer', () => {
diff --git a/aio/src/app/app.component.ts b/aio/src/app/app.component.ts
index 26afa78a82..7f20212c82 100644
--- a/aio/src/app/app.component.ts
+++ b/aio/src/app/app.component.ts
@@ -275,6 +275,25 @@ export class AppComponent implements OnInit {
this.tocMaxHeight = (document.body.scrollHeight - window.pageYOffset - this.tocMaxHeightOffset).toFixed(2);
}
+ // Restrain scrolling inside an element, when the cursor is over it
+ restrainScrolling(evt: WheelEvent) {
+ const elem = evt.currentTarget as Element;
+ const scrollTop = elem.scrollTop;
+
+ if (evt.deltaY < 0) {
+ // Trying to scroll up: Prevent scrolling if already at the top.
+ if (scrollTop < 1) {
+ evt.preventDefault();
+ }
+ } else {
+ // Trying to scroll down: Prevent scrolling if already at the bottom.
+ const maxScrollTop = elem.scrollHeight - elem.clientHeight;
+ if (maxScrollTop - scrollTop < 1) {
+ evt.preventDefault();
+ }
+ }
+ }
+
// Search related methods and handlers