Skip to content

Commit

Permalink
fix(modal): scrollbar issue with stacked modals (#4256)
Browse files Browse the repository at this point in the history
fix #4255
  • Loading branch information
divdavem committed Feb 21, 2022
1 parent 6f1a45d commit 9da624d
Show file tree
Hide file tree
Showing 2 changed files with 44 additions and 2 deletions.
20 changes: 18 additions & 2 deletions src/modal/modal-stack.ts
Expand Up @@ -26,6 +26,7 @@ import {NgbModalWindow} from './modal-window';
export class NgbModalStack {
private _activeWindowCmptHasChanged = new Subject<void>();
private _ariaHiddenValues: Map<Element, string | null> = new Map();
private _scrollBarRestoreFn: null | (() => void) = null;
private _backdropAttributes = ['animation', 'backdropClass'];
private _modalRefs: NgbModalRef[] = [];
private _windowAttributes = [
Expand All @@ -49,17 +50,31 @@ export class NgbModalStack {
});
}

private _restoreScrollBar() {
const scrollBarRestoreFn = this._scrollBarRestoreFn;
if (scrollBarRestoreFn) {
this._scrollBarRestoreFn = null;
scrollBarRestoreFn();
}
}

private _hideScrollBar() {
if (!this._scrollBarRestoreFn) {
this._scrollBarRestoreFn = this._scrollBar.hide();
}
}

open(moduleCFR: ComponentFactoryResolver, contentInjector: Injector, content: any, options: NgbModalOptions):
NgbModalRef {
const containerEl = options.container instanceof HTMLElement ? options.container : isDefined(options.container) ?
this._document.querySelector(options.container) :
this._document.body;
const renderer = this._rendererFactory.createRenderer(null, null);

const revertScrollBar = this._scrollBar.hide();
const removeBodyClass = () => {
if (!this._modalRefs.length) {
renderer.removeClass(this._document.body, 'modal-open');
this._restoreScrollBar();
this._revertAriaHidden();
}
};
Expand All @@ -68,6 +83,8 @@ export class NgbModalStack {
throw new Error(`The specified modal container "${options.container || 'body'}" was not found in the DOM.`);
}

this._hideScrollBar();

const activeModal = new NgbActiveModal();
const contentRef =
this._getContentRef(moduleCFR, options.injector || contentInjector, content, activeModal, options);
Expand All @@ -79,7 +96,6 @@ export class NgbModalStack {

this._registerModalRef(ngbModalRef);
this._registerWindowCmpt(windowCmptRef);
ngbModalRef.result.then(revertScrollBar, revertScrollBar);
ngbModalRef.result.then(removeBodyClass, removeBodyClass);
activeModal.close = (result: any) => { ngbModalRef.close(result); };
activeModal.dismiss = (reason: any) => { ngbModalRef.dismiss(reason); };
Expand Down
26 changes: 26 additions & 0 deletions src/modal/modal.spec.ts
Expand Up @@ -297,6 +297,32 @@ describe('ngb-modal', () => {
expect(document.body).not.toHaveCssClass('modal-open');
}));

it('should remove / restore scroll bar when multiple stacked modals are open and closed', fakeAsync(() => {
expect(window.getComputedStyle(document.body).overflow).not.toBe('hidden');
const modal1Ref = fixture.componentInstance.open('bar');
fixture.detectChanges();
expect(document.body).toHaveCssClass('modal-open');
expect(window.getComputedStyle(document.body).overflow).toBe('hidden');

const modal2Ref = fixture.componentInstance.open('baz');
fixture.detectChanges();
tick();
expect(document.body).toHaveCssClass('modal-open');
expect(window.getComputedStyle(document.body).overflow).toBe('hidden');

modal1Ref.close('bar result');
fixture.detectChanges();
tick();
expect(document.body).toHaveCssClass('modal-open');
expect(window.getComputedStyle(document.body).overflow).toBe('hidden');

modal2Ref.close('baz result');
fixture.detectChanges();
tick();
expect(document.body).not.toHaveCssClass('modal-open');
expect(window.getComputedStyle(document.body).overflow).not.toBe('hidden');
}));

it('should not throw when close called multiple times', () => {
const modalInstance = fixture.componentInstance.open('foo');
fixture.detectChanges();
Expand Down

0 comments on commit 9da624d

Please sign in to comment.