diff --git a/.bundlewatch.config.json b/.bundlewatch.config.json index 2c89f7ea47a8..eb14781be686 100644 --- a/.bundlewatch.config.json +++ b/.bundlewatch.config.json @@ -34,7 +34,7 @@ }, { "path": "./dist/js/bootstrap.bundle.js", - "maxSize": "82.5 kB" + "maxSize": "82.75 kB" }, { "path": "./dist/js/bootstrap.bundle.min.js", @@ -42,7 +42,7 @@ }, { "path": "./dist/js/bootstrap.js", - "maxSize": "53.75 kB" + "maxSize": "54.0 kB" }, { "path": "./dist/js/bootstrap.min.js", diff --git a/js/src/dialog-base.js b/js/src/dialog-base.js index 29c66278311b..0aaf5ff8e453 100644 --- a/js/src/dialog-base.js +++ b/js/src/dialog-base.js @@ -117,6 +117,17 @@ class DialogBase extends BaseComponent { }, this._element, this._isAnimated()) } + dispose() { + // If disposed while still open, close the native and restore body + // scroll. Otherwise `dialog-open` (overflow: hidden) would stay stuck on the + // body — e.g. when an SPA tears the component down mid-navigation. + if (this._element.open) { + this._closeAndCleanup() + } + + super.dispose() + } + // Protected — hooks for subclasses to override _getShowOptions() { diff --git a/js/src/dialog.js b/js/src/dialog.js index c87c81445593..2542f242f788 100644 --- a/js/src/dialog.js +++ b/js/src/dialog.js @@ -119,7 +119,7 @@ EventHandler.on(document, EVENT_CLICK_DATA_API, SELECTOR_DATA_TOGGLE, function ( EventHandler.one(target, EVENT_HIDDEN, () => { if (isVisible(this)) { - this.focus() + this.focus({ preventScroll: true }) } }) }) diff --git a/js/src/drawer.js b/js/src/drawer.js index ce71ad0f8cb0..3c7ef67aec9f 100644 --- a/js/src/drawer.js +++ b/js/src/drawer.js @@ -149,7 +149,7 @@ EventHandler.on(document, EVENT_CLICK_DATA_API, SELECTOR_DATA_TOGGLE, function ( EventHandler.one(target, EVENT_HIDDEN, () => { if (isVisible(this)) { - this.focus() + this.focus({ preventScroll: true }) } }) diff --git a/js/tests/unit/dialog.spec.js b/js/tests/unit/dialog.spec.js index c763abe5d25c..5e6c40773089 100644 --- a/js/tests/unit/dialog.spec.js +++ b/js/tests/unit/dialog.spec.js @@ -713,6 +713,28 @@ describe('Dialog', () => { expect(Dialog.getInstance(dialogEl)).toBeNull() expect(spyOff).toHaveBeenCalled() }) + + it('should close the dialog and restore body scroll when disposed while open', () => { + return new Promise(resolve => { + fixtureEl.innerHTML = '' + + const dialogEl = fixtureEl.querySelector('.dialog') + const dialog = new Dialog(dialogEl) + + dialogEl.addEventListener('shown.bs.dialog', () => { + expect(dialogEl.open).toBeTrue() + expect(document.body.classList.contains('dialog-open')).toBeTrue() + + dialog.dispose() + + expect(dialogEl.open).toBeFalse() + expect(document.body.classList.contains('dialog-open')).toBeFalse() + resolve() + }) + + dialog.show() + }) + }) }) describe('data-api', () => { @@ -803,7 +825,7 @@ describe('Dialog', () => { const hideListener = () => { setTimeout(() => { - expect(spy).toHaveBeenCalled() + expect(spy).toHaveBeenCalledWith({ preventScroll: true }) resolve() }, 20) } diff --git a/js/tests/unit/drawer.spec.js b/js/tests/unit/drawer.spec.js index a57cbeb1ab83..3cc31cb03010 100644 --- a/js/tests/unit/drawer.spec.js +++ b/js/tests/unit/drawer.spec.js @@ -570,6 +570,28 @@ describe('Drawer', () => { expect(Drawer.getInstance(drawerEl)).toBeNull() expect(spyOff).toHaveBeenCalled() }) + + it('should close the drawer and restore body scroll when disposed while open', () => { + return new Promise(resolve => { + fixtureEl.innerHTML = '' + + const drawerEl = fixtureEl.querySelector('dialog') + const drawer = new Drawer(drawerEl) + + drawerEl.addEventListener('shown.bs.drawer', () => { + expect(drawerEl.open).toBeTrue() + expect(document.body.classList.contains('dialog-open')).toBeTrue() + + drawer.dispose() + + expect(drawerEl.open).toBeFalse() + expect(document.body.classList.contains('dialog-open')).toBeFalse() + resolve() + }) + + drawer.show() + }) + }) }) describe('data-api', () => { @@ -649,7 +671,7 @@ describe('Drawer', () => { }) drawerEl.addEventListener('hidden.bs.drawer', () => { setTimeout(() => { - expect(spy).toHaveBeenCalled() + expect(spy).toHaveBeenCalledWith({ preventScroll: true }) resolve() }, 5) })