Skip to content
This repository was archived by the owner on Jan 13, 2025. It is now read-only.

Commit 8686d85

Browse files
vinhlhtraviskaufman
authored andcommitted
fix(drawer): Prevent scrolling on body when temporary drawer open (#807)
Closes #777 BREAKING CHANGE: Adapter API for temporary drawers contains two new methods: `addBodyClass` and `removeBodyClass`.
1 parent dec20ab commit 8686d85

File tree

8 files changed

+75
-7
lines changed

8 files changed

+75
-7
lines changed

packages/mdc-drawer/README.md

+2
Original file line numberDiff line numberDiff line change
@@ -402,6 +402,8 @@ The adapter for temporary drawers must provide the following functions, with cor
402402
| `addClass(className: string) => void` | Adds a class to the root element. |
403403
| `removeClass(className: string) => void` | Removes a class from the root element. |
404404
| `hasClass(className: string) => boolean` | Returns boolean indicating whether element has a given class. |
405+
| `addBodyClass(className: string) => void` | Adds a class to the body. |
406+
| `removeBodyClass(className: string) => void` | Removes a class from the body. |
405407
| `hasNecessaryDom() => boolean` | Returns boolean indicating whether the necessary DOM is present (namely, the `mdc-temporary-drawer__drawer` drawer container). |
406408
| `registerInteractionHandler(evt: string, handler: EventListener) => void` | Adds an event listener to the root element, for the specified event name. |
407409
| `deregisterInteractionHandler(evt: string, handler: EventListener) => void` | Removes an event listener from the root element, for the specified event name. |

packages/mdc-drawer/slidable/foundation.js

+8-6
Original file line numberDiff line numberDiff line change
@@ -51,12 +51,7 @@ export class MDCSlidableDrawerFoundation extends MDCFoundation {
5151
this.animatingCssClass_ = animatingCssClass;
5252
this.openCssClass_ = openCssClass;
5353

54-
this.transitionEndHandler_ = (ev) => {
55-
if (this.isRootTransitioningEventTarget_(ev.target)) {
56-
this.adapter_.removeClass(this.animatingCssClass_);
57-
this.adapter_.deregisterTransitionEndHandler(this.transitionEndHandler_);
58-
}
59-
};
54+
this.transitionEndHandler_ = (evt) => this.handleTransitionEnd_(evt);
6055

6156
this.inert_ = false;
6257

@@ -239,4 +234,11 @@ export class MDCSlidableDrawerFoundation extends MDCFoundation {
239234
// if the event target is the root event target currently transitioning.
240235
return false;
241236
}
237+
238+
handleTransitionEnd_(evt) {
239+
if (this.isRootTransitioningEventTarget_(evt.target)) {
240+
this.adapter_.removeClass(this.animatingCssClass_);
241+
this.adapter_.deregisterTransitionEndHandler(this.transitionEndHandler_);
242+
}
243+
};
242244
}

packages/mdc-drawer/temporary/constants.js

+1
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ export const cssClasses = {
2020
ROOT: 'mdc-temporary-drawer',
2121
OPEN: 'mdc-temporary-drawer--open',
2222
ANIMATING: 'mdc-temporary-drawer--animating',
23+
SCROLL_LOCK: 'mdc-drawer-scroll-lock',
2324
};
2425

2526
export const strings = {

packages/mdc-drawer/temporary/foundation.js

+19
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,8 @@ export default class MDCTemporaryDrawerFoundation extends MDCSlidableDrawerFound
2828

2929
static get defaultAdapter() {
3030
return Object.assign(MDCSlidableDrawerFoundation.defaultAdapter, {
31+
addBodyClass: (/* className: string */) => {},
32+
removeBodyClass: (/* className: string */) => {},
3133
isDrawer: () => false,
3234
updateCssVariable: (/* value: string */) => {},
3335
});
@@ -56,9 +58,11 @@ export default class MDCTemporaryDrawerFoundation extends MDCSlidableDrawerFound
5658
super.destroy();
5759

5860
this.adapter_.deregisterInteractionHandler('click', this.componentClickHandler_);
61+
this.enableScroll_();
5962
}
6063

6164
open() {
65+
this.disableScroll_();
6266
// Make sure custom property values are cleared before starting.
6367
this.adapter_.updateCssVariable('');
6468

@@ -88,4 +92,19 @@ export default class MDCTemporaryDrawerFoundation extends MDCSlidableDrawerFound
8892
isRootTransitioningEventTarget_(el) {
8993
return this.adapter_.isDrawer(el);
9094
}
95+
96+
handleTransitionEnd_(evt) {
97+
super.handleTransitionEnd_(evt);
98+
if (!this.isOpen_) {
99+
this.enableScroll_();
100+
}
101+
};
102+
103+
disableScroll_() {
104+
this.adapter_.addBodyClass(cssClasses.SCROLL_LOCK);
105+
}
106+
107+
enableScroll_() {
108+
this.adapter_.removeBodyClass(cssClasses.SCROLL_LOCK);
109+
}
91110
}

packages/mdc-drawer/temporary/index.js

+2
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,8 @@ export class MDCTemporaryDrawer extends MDCComponent {
5050
addClass: (className) => this.root_.classList.add(className),
5151
removeClass: (className) => this.root_.classList.remove(className),
5252
hasClass: (className) => this.root_.classList.contains(className),
53+
addBodyClass: (className) => document.body.classList.add(className),
54+
removeBodyClass: (className) => document.body.classList.remove(className),
5355
hasNecessaryDom: () => Boolean(this.drawer),
5456
registerInteractionHandler: (evt, handler) =>
5557
this.root_.addEventListener(util.remapEvent(evt), handler, util.applyPassive()),

packages/mdc-drawer/temporary/mdc-temporary-drawer.scss

+5
Original file line numberDiff line numberDiff line change
@@ -135,3 +135,8 @@
135135
}
136136
}
137137
}
138+
139+
.mdc-drawer-scroll-lock {
140+
height: 100vh;
141+
overflow: hidden;
142+
}

test/unit/mdc-drawer/mdc-temporary-drawer.test.js

+15
Original file line numberDiff line numberDiff line change
@@ -322,3 +322,18 @@ test('adapter#isDrawer returns false for a non-drawer element', () => {
322322
const {root, component} = setupTest();
323323
assert.isNotOk(component.getDefaultFoundation().adapter_.isDrawer(root));
324324
});
325+
326+
test('adapter#addBodyClass adds a class to the body', () => {
327+
const {component} = setupTest();
328+
component.getDefaultFoundation().adapter_.addBodyClass('mdc-drawer--scroll-lock');
329+
assert.isOk(document.querySelector('body').classList.contains('mdc-drawer--scroll-lock'));
330+
});
331+
332+
test('adapter#removeBodyClass remove a class from the body', () => {
333+
const {component} = setupTest();
334+
const body = document.querySelector('body');
335+
336+
body.classList.add('mdc-drawer--scroll-lock');
337+
component.getDefaultFoundation().adapter_.removeBodyClass('mdc-drawer--scroll-lock');
338+
assert.isNotOk(body.classList.contains('mdc-drawer--scroll-lock'));
339+
});

test/unit/mdc-drawer/temporary.foundation.test.js

+23-1
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,8 @@ test('exports cssClasses', () => {
4242

4343
test('defaultAdapter returns a complete adapter implementation', () => {
4444
verifyDefaultAdapter(MDCTemporaryDrawerFoundation, [
45-
'addClass', 'removeClass', 'hasClass', 'hasNecessaryDom', 'registerInteractionHandler',
45+
'addClass', 'removeClass', 'hasClass', 'addBodyClass', 'removeBodyClass',
46+
'hasNecessaryDom', 'registerInteractionHandler',
4647
'deregisterInteractionHandler', 'registerDrawerInteractionHandler', 'deregisterDrawerInteractionHandler',
4748
'registerTransitionEndHandler', 'deregisterTransitionEndHandler', 'registerDocumentKeydownHandler',
4849
'deregisterDocumentKeydownHandler', 'setTranslateX', 'getFocusableElements',
@@ -320,3 +321,24 @@ test('#isRootTransitioningEventTarget_ returns true if the element is the drawer
320321
td.when(mockAdapter.isDrawer(fakeEl)).thenReturn(true);
321322
assert.isTrue(foundation.isRootTransitioningEventTarget_(fakeEl));
322323
});
324+
325+
test('#open adds scroll lock class to the body', () => {
326+
const {foundation, mockAdapter} = setupTest();
327+
328+
foundation.open();
329+
330+
td.verify(mockAdapter.addBodyClass(cssClasses.SCROLL_LOCK));
331+
});
332+
333+
test('#close removes the scroll lock class from the body', () => {
334+
const {foundation, mockAdapter} = setupTest();
335+
336+
td.when(mockAdapter.registerTransitionEndHandler(td.callback)).thenCallback({target: {}});
337+
td.when(mockAdapter.isDrawer(td.matchers.isA(Object))).thenReturn(true);
338+
foundation.open();
339+
td.when(mockAdapter.registerTransitionEndHandler(td.callback)).thenCallback({target: {}});
340+
td.when(mockAdapter.isDrawer(td.matchers.isA(Object))).thenReturn(true);
341+
foundation.close();
342+
343+
td.verify(mockAdapter.removeBodyClass(cssClasses.SCROLL_LOCK));
344+
});

0 commit comments

Comments
 (0)