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