diff --git a/packages/mdc-drawer/README.md b/packages/mdc-drawer/README.md index b837953b67b..4d661406807 100644 --- a/packages/mdc-drawer/README.md +++ b/packages/mdc-drawer/README.md @@ -402,6 +402,8 @@ The adapter for temporary drawers must provide the following functions, with cor | `addClass(className: string) => void` | Adds a class to the root element. | | `removeClass(className: string) => void` | Removes a class from the root element. | | `hasClass(className: string) => boolean` | Returns boolean indicating whether element has a given class. | +| `addBodyClass(className: string) => void` | Adds a class to the body. | +| `removeBodyClass(className: string) => void` | Removes a class from the body. | | `hasNecessaryDom() => boolean` | Returns boolean indicating whether the necessary DOM is present (namely, the `mdc-temporary-drawer__drawer` drawer container). | | `registerInteractionHandler(evt: string, handler: EventListener) => void` | Adds an event listener to the root element, for the specified event name. | | `deregisterInteractionHandler(evt: string, handler: EventListener) => void` | Removes an event listener from the root element, for the specified event name. | diff --git a/packages/mdc-drawer/slidable/foundation.js b/packages/mdc-drawer/slidable/foundation.js index 23aaab6529a..7a1507708d0 100644 --- a/packages/mdc-drawer/slidable/foundation.js +++ b/packages/mdc-drawer/slidable/foundation.js @@ -51,12 +51,7 @@ export class MDCSlidableDrawerFoundation extends MDCFoundation { this.animatingCssClass_ = animatingCssClass; this.openCssClass_ = openCssClass; - this.transitionEndHandler_ = (ev) => { - if (this.isRootTransitioningEventTarget_(ev.target)) { - this.adapter_.removeClass(this.animatingCssClass_); - this.adapter_.deregisterTransitionEndHandler(this.transitionEndHandler_); - } - }; + this.transitionEndHandler_ = (evt) => this.handleTransitionEnd_(evt); this.inert_ = false; @@ -239,4 +234,11 @@ export class MDCSlidableDrawerFoundation extends MDCFoundation { // if the event target is the root event target currently transitioning. return false; } + + handleTransitionEnd_(evt) { + if (this.isRootTransitioningEventTarget_(evt.target)) { + this.adapter_.removeClass(this.animatingCssClass_); + this.adapter_.deregisterTransitionEndHandler(this.transitionEndHandler_); + } + }; } diff --git a/packages/mdc-drawer/temporary/constants.js b/packages/mdc-drawer/temporary/constants.js index 0ab674ba9d9..deb75f72a06 100644 --- a/packages/mdc-drawer/temporary/constants.js +++ b/packages/mdc-drawer/temporary/constants.js @@ -20,6 +20,7 @@ export const cssClasses = { ROOT: 'mdc-temporary-drawer', OPEN: 'mdc-temporary-drawer--open', ANIMATING: 'mdc-temporary-drawer--animating', + SCROLL_LOCK: 'mdc-drawer-scroll-lock', }; export const strings = { diff --git a/packages/mdc-drawer/temporary/foundation.js b/packages/mdc-drawer/temporary/foundation.js index d3f40c96d1e..c68535c4ab6 100644 --- a/packages/mdc-drawer/temporary/foundation.js +++ b/packages/mdc-drawer/temporary/foundation.js @@ -28,6 +28,8 @@ export default class MDCTemporaryDrawerFoundation extends MDCSlidableDrawerFound static get defaultAdapter() { return Object.assign(MDCSlidableDrawerFoundation.defaultAdapter, { + addBodyClass: (/* className: string */) => {}, + removeBodyClass: (/* className: string */) => {}, isDrawer: () => false, updateCssVariable: (/* value: string */) => {}, }); @@ -56,9 +58,11 @@ export default class MDCTemporaryDrawerFoundation extends MDCSlidableDrawerFound super.destroy(); this.adapter_.deregisterInteractionHandler('click', this.componentClickHandler_); + this.enableScroll_(); } open() { + this.disableScroll_(); // Make sure custom property values are cleared before starting. this.adapter_.updateCssVariable(''); @@ -88,4 +92,19 @@ export default class MDCTemporaryDrawerFoundation extends MDCSlidableDrawerFound isRootTransitioningEventTarget_(el) { return this.adapter_.isDrawer(el); } + + handleTransitionEnd_(evt) { + super.handleTransitionEnd_(evt); + if (!this.isOpen_) { + this.enableScroll_(); + } + }; + + disableScroll_() { + this.adapter_.addBodyClass(cssClasses.SCROLL_LOCK); + } + + enableScroll_() { + this.adapter_.removeBodyClass(cssClasses.SCROLL_LOCK); + } } diff --git a/packages/mdc-drawer/temporary/index.js b/packages/mdc-drawer/temporary/index.js index 62230445bc4..1e4bab081c4 100644 --- a/packages/mdc-drawer/temporary/index.js +++ b/packages/mdc-drawer/temporary/index.js @@ -50,6 +50,8 @@ export class MDCTemporaryDrawer extends MDCComponent { addClass: (className) => this.root_.classList.add(className), removeClass: (className) => this.root_.classList.remove(className), hasClass: (className) => this.root_.classList.contains(className), + addBodyClass: (className) => document.body.classList.add(className), + removeBodyClass: (className) => document.body.classList.remove(className), hasNecessaryDom: () => Boolean(this.drawer), registerInteractionHandler: (evt, handler) => this.root_.addEventListener(util.remapEvent(evt), handler, util.applyPassive()), diff --git a/packages/mdc-drawer/temporary/mdc-temporary-drawer.scss b/packages/mdc-drawer/temporary/mdc-temporary-drawer.scss index bcada887525..c29d3e99ffc 100644 --- a/packages/mdc-drawer/temporary/mdc-temporary-drawer.scss +++ b/packages/mdc-drawer/temporary/mdc-temporary-drawer.scss @@ -135,3 +135,8 @@ } } } + +.mdc-drawer-scroll-lock { + height: 100vh; + overflow: hidden; +} diff --git a/test/unit/mdc-drawer/mdc-temporary-drawer.test.js b/test/unit/mdc-drawer/mdc-temporary-drawer.test.js index bcb2a42f682..aad35ddbfa4 100644 --- a/test/unit/mdc-drawer/mdc-temporary-drawer.test.js +++ b/test/unit/mdc-drawer/mdc-temporary-drawer.test.js @@ -322,3 +322,18 @@ test('adapter#isDrawer returns false for a non-drawer element', () => { const {root, component} = setupTest(); assert.isNotOk(component.getDefaultFoundation().adapter_.isDrawer(root)); }); + +test('adapter#addBodyClass adds a class to the body', () => { + const {component} = setupTest(); + component.getDefaultFoundation().adapter_.addBodyClass('mdc-drawer--scroll-lock'); + assert.isOk(document.querySelector('body').classList.contains('mdc-drawer--scroll-lock')); +}); + +test('adapter#removeBodyClass remove a class from the body', () => { + const {component} = setupTest(); + const body = document.querySelector('body'); + + body.classList.add('mdc-drawer--scroll-lock'); + component.getDefaultFoundation().adapter_.removeBodyClass('mdc-drawer--scroll-lock'); + assert.isNotOk(body.classList.contains('mdc-drawer--scroll-lock')); +}); diff --git a/test/unit/mdc-drawer/temporary.foundation.test.js b/test/unit/mdc-drawer/temporary.foundation.test.js index f9e20a2a625..e91b838e6b4 100644 --- a/test/unit/mdc-drawer/temporary.foundation.test.js +++ b/test/unit/mdc-drawer/temporary.foundation.test.js @@ -42,7 +42,8 @@ test('exports cssClasses', () => { test('defaultAdapter returns a complete adapter implementation', () => { verifyDefaultAdapter(MDCTemporaryDrawerFoundation, [ - 'addClass', 'removeClass', 'hasClass', 'hasNecessaryDom', 'registerInteractionHandler', + 'addClass', 'removeClass', 'hasClass', 'addBodyClass', 'removeBodyClass', + 'hasNecessaryDom', 'registerInteractionHandler', 'deregisterInteractionHandler', 'registerDrawerInteractionHandler', 'deregisterDrawerInteractionHandler', 'registerTransitionEndHandler', 'deregisterTransitionEndHandler', 'registerDocumentKeydownHandler', 'deregisterDocumentKeydownHandler', 'setTranslateX', 'getFocusableElements', @@ -320,3 +321,24 @@ test('#isRootTransitioningEventTarget_ returns true if the element is the drawer td.when(mockAdapter.isDrawer(fakeEl)).thenReturn(true); assert.isTrue(foundation.isRootTransitioningEventTarget_(fakeEl)); }); + +test('#open adds scroll lock class to the body', () => { + const {foundation, mockAdapter} = setupTest(); + + foundation.open(); + + td.verify(mockAdapter.addBodyClass(cssClasses.SCROLL_LOCK)); +}); + +test('#close removes the scroll lock class from the body', () => { + const {foundation, mockAdapter} = setupTest(); + + td.when(mockAdapter.registerTransitionEndHandler(td.callback)).thenCallback({target: {}}); + td.when(mockAdapter.isDrawer(td.matchers.isA(Object))).thenReturn(true); + foundation.open(); + td.when(mockAdapter.registerTransitionEndHandler(td.callback)).thenCallback({target: {}}); + td.when(mockAdapter.isDrawer(td.matchers.isA(Object))).thenReturn(true); + foundation.close(); + + td.verify(mockAdapter.removeBodyClass(cssClasses.SCROLL_LOCK)); +});