From: Julien Déramond Date: Sat, 20 Jun 2026 20:57:31 +0000 (+0200) Subject: Drop deprecated `.modal-backdrop` class, and unused `util/backdrop.js` (#42538) X-Git-Url: http://git.ipfire.org/gitweb.cgi?a=commitdiff_plain;h=f4819cd43c69434c65efb90d758c14e76f476ed2;p=thirdparty%2Fbootstrap.git Drop deprecated `.modal-backdrop` class, and unused `util/backdrop.js` (#42538) --- diff --git a/js/src/util/backdrop.js b/js/src/util/backdrop.js deleted file mode 100644 index 82b54900e4..0000000000 --- a/js/src/util/backdrop.js +++ /dev/null @@ -1,151 +0,0 @@ -/** - * -------------------------------------------------------------------------- - * Bootstrap util/backdrop.js - * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE) - * -------------------------------------------------------------------------- - */ - -import EventHandler from '../dom/event-handler.js' -import Config from './config.js' -import { - execute, executeAfterTransition, getElement, reflow -} from './index.js' - -/** - * Constants - */ - -const NAME = 'backdrop' -const CLASS_NAME_FADE = 'fade' -const CLASS_NAME_SHOW = 'show' -const EVENT_MOUSEDOWN = `mousedown.bs.${NAME}` - -const Default = { - className: 'modal-backdrop', - clickCallback: null, - isAnimated: false, - isVisible: true, // if false, we use the backdrop helper without adding any element to the dom - rootElement: 'body' // give the choice to place backdrop under different elements -} - -const DefaultType = { - className: 'string', - clickCallback: '(function|null)', - isAnimated: 'boolean', - isVisible: 'boolean', - rootElement: '(element|string)' -} - -/** - * Class definition - */ - -class Backdrop extends Config { - constructor(config) { - super() - this._config = this._getConfig(config) - this._isAppended = false - this._element = null - } - - // Getters - static get Default() { - return Default - } - - static get DefaultType() { - return DefaultType - } - - static get NAME() { - return NAME - } - - // Public - show(callback) { - if (!this._config.isVisible) { - execute(callback) - return - } - - this._append() - - const element = this._getElement() - if (this._config.isAnimated) { - reflow(element) - } - - element.classList.add(CLASS_NAME_SHOW) - - this._emulateAnimation(() => { - execute(callback) - }) - } - - hide(callback) { - if (!this._config.isVisible) { - execute(callback) - return - } - - this._getElement().classList.remove(CLASS_NAME_SHOW) - - this._emulateAnimation(() => { - this.dispose() - execute(callback) - }) - } - - dispose() { - if (!this._isAppended) { - return - } - - EventHandler.off(this._element, EVENT_MOUSEDOWN) - - this._element.remove() - this._isAppended = false - } - - // Private - _getElement() { - if (!this._element) { - const backdrop = document.createElement('div') - backdrop.className = this._config.className - if (this._config.isAnimated) { - backdrop.classList.add(CLASS_NAME_FADE) - } - - this._element = backdrop - } - - return this._element - } - - _configAfterMerge(config) { - // use getElement() with the default "body" to get a fresh Element on each instantiation - config.rootElement = getElement(config.rootElement) - return config - } - - _append() { - if (this._isAppended) { - return - } - - const element = this._getElement() - this._config.rootElement.append(element) - - EventHandler.on(element, EVENT_MOUSEDOWN, () => { - execute(this._config.clickCallback) - }) - - this._isAppended = true - } - - _emulateAnimation(callback) { - executeAfterTransition(callback, this._getElement(), this._config.isAnimated) - } -} - -export default Backdrop diff --git a/js/tests/unit/util/backdrop.spec.js b/js/tests/unit/util/backdrop.spec.js deleted file mode 100644 index 0faaac6a5c..0000000000 --- a/js/tests/unit/util/backdrop.spec.js +++ /dev/null @@ -1,321 +0,0 @@ -import Backdrop from '../../../src/util/backdrop.js' -import { getTransitionDurationFromElement } from '../../../src/util/index.js' -import { clearFixture, getFixture } from '../../helpers/fixture.js' - -const CLASS_BACKDROP = '.modal-backdrop' -const CLASS_NAME_FADE = 'fade' -const CLASS_NAME_SHOW = 'show' - -describe('Backdrop', () => { - let fixtureEl - - beforeAll(() => { - fixtureEl = getFixture() - }) - - afterEach(() => { - clearFixture() - const list = document.querySelectorAll(CLASS_BACKDROP) - - for (const el of list) { - el.remove() - } - }) - - describe('show', () => { - it('should append the backdrop html once on show and include the "show" class if it is "shown"', () => { - return new Promise(resolve => { - const instance = new Backdrop({ - isVisible: true, - isAnimated: false - }) - const getElements = () => document.querySelectorAll(CLASS_BACKDROP) - - expect(getElements()).toHaveSize(0) - - instance.show() - instance.show(() => { - expect(getElements()).toHaveSize(1) - for (const el of getElements()) { - expect(el).toHaveClass(CLASS_NAME_SHOW) - } - - resolve() - }) - }) - }) - - it('should not append the backdrop html if it is not "shown"', () => { - return new Promise(resolve => { - const instance = new Backdrop({ - isVisible: false, - isAnimated: true - }) - const getElements = () => document.querySelectorAll(CLASS_BACKDROP) - - expect(getElements()).toHaveSize(0) - instance.show(() => { - expect(getElements()).toHaveSize(0) - resolve() - }) - }) - }) - - it('should append the backdrop html once and include the "fade" class if it is "shown" and "animated"', () => { - return new Promise(resolve => { - const instance = new Backdrop({ - isVisible: true, - isAnimated: true - }) - const getElements = () => document.querySelectorAll(CLASS_BACKDROP) - - expect(getElements()).toHaveSize(0) - - instance.show(() => { - expect(getElements()).toHaveSize(1) - for (const el of getElements()) { - expect(el).toHaveClass(CLASS_NAME_FADE) - } - - resolve() - }) - }) - }) - }) - - describe('hide', () => { - it('should remove the backdrop html', () => { - return new Promise(resolve => { - const instance = new Backdrop({ - isVisible: true, - isAnimated: true - }) - - const getElements = () => document.body.querySelectorAll(CLASS_BACKDROP) - - expect(getElements()).toHaveSize(0) - instance.show(() => { - expect(getElements()).toHaveSize(1) - instance.hide(() => { - expect(getElements()).toHaveSize(0) - resolve() - }) - }) - }) - }) - - it('should remove the "show" class', () => { - return new Promise(resolve => { - const instance = new Backdrop({ - isVisible: true, - isAnimated: true - }) - const elem = instance._getElement() - - instance.show() - instance.hide(() => { - expect(elem).not.toHaveClass(CLASS_NAME_SHOW) - resolve() - }) - }) - }) - - it('should not try to remove Node on remove method if it is not "shown"', () => { - return new Promise(resolve => { - const instance = new Backdrop({ - isVisible: false, - isAnimated: true - }) - const getElements = () => document.querySelectorAll(CLASS_BACKDROP) - const spy = spyOn(instance, 'dispose').and.callThrough() - - expect(getElements()).toHaveSize(0) - expect(instance._isAppended).toBeFalse() - instance.show(() => { - instance.hide(() => { - expect(getElements()).toHaveSize(0) - expect(spy).not.toHaveBeenCalled() - expect(instance._isAppended).toBeFalse() - resolve() - }) - }) - }) - }) - - it('should not error if the backdrop no longer has a parent', () => { - return new Promise(resolve => { - fixtureEl.innerHTML = '
' - - const wrapper = fixtureEl.querySelector('#wrapper') - const instance = new Backdrop({ - isVisible: true, - isAnimated: true, - rootElement: wrapper - }) - - const getElements = () => document.querySelectorAll(CLASS_BACKDROP) - - instance.show(() => { - wrapper.remove() - instance.hide(() => { - expect(getElements()).toHaveSize(0) - resolve() - }) - }) - }) - }) - }) - - describe('click callback', () => { - it('should execute callback on click', () => { - return new Promise(resolve => { - const spy = jasmine.createSpy('spy') - - const instance = new Backdrop({ - isVisible: true, - isAnimated: false, - clickCallback: () => spy() - }) - const endTest = () => { - setTimeout(() => { - expect(spy).toHaveBeenCalled() - resolve() - }, 10) - } - - instance.show(() => { - const clickEvent = new Event('mousedown', { bubbles: true, cancelable: true }) - document.querySelector(CLASS_BACKDROP).dispatchEvent(clickEvent) - endTest() - }) - }) - }) - - describe('animation callbacks', () => { - it('should show and hide backdrop after counting transition duration if it is animated', () => { - return new Promise(resolve => { - const instance = new Backdrop({ - isVisible: true, - isAnimated: true - }) - const spy2 = jasmine.createSpy('spy2') - - const execDone = () => { - setTimeout(() => { - expect(spy2).toHaveBeenCalledTimes(2) - resolve() - }, 10) - } - - instance.show(spy2) - instance.hide(() => { - spy2() - execDone() - }) - expect(spy2).not.toHaveBeenCalled() - }) - }) - - it('should show and hide backdrop without a delay if it is not animated', () => { - return new Promise(resolve => { - const spy = jasmine.createSpy('spy', getTransitionDurationFromElement) - const instance = new Backdrop({ - isVisible: true, - isAnimated: false - }) - const spy2 = jasmine.createSpy('spy2') - - instance.show(spy2) - instance.hide(spy2) - - setTimeout(() => { - expect(spy2).toHaveBeenCalled() - expect(spy).not.toHaveBeenCalled() - resolve() - }, 10) - }) - }) - - it('should not call delay callbacks if it is not "shown"', () => { - return new Promise(resolve => { - const instance = new Backdrop({ - isVisible: false, - isAnimated: true - }) - const spy = jasmine.createSpy('spy', getTransitionDurationFromElement) - - instance.show() - instance.hide(() => { - expect(spy).not.toHaveBeenCalled() - resolve() - }) - }) - }) - }) - - describe('Config', () => { - describe('rootElement initialization', () => { - it('should be appended on "document.body" by default', () => { - return new Promise(resolve => { - const instance = new Backdrop({ - isVisible: true - }) - const getElement = () => document.querySelector(CLASS_BACKDROP) - instance.show(() => { - expect(getElement().parentElement).toEqual(document.body) - resolve() - }) - }) - }) - - it('should find the rootElement if passed as a string', () => { - return new Promise(resolve => { - const instance = new Backdrop({ - isVisible: true, - rootElement: 'body' - }) - const getElement = () => document.querySelector(CLASS_BACKDROP) - instance.show(() => { - expect(getElement().parentElement).toEqual(document.body) - resolve() - }) - }) - }) - - it('should be appended on any element given by the proper config', () => { - return new Promise(resolve => { - fixtureEl.innerHTML = '
' - - const wrapper = fixtureEl.querySelector('#wrapper') - const instance = new Backdrop({ - isVisible: true, - rootElement: wrapper - }) - const getElement = () => document.querySelector(CLASS_BACKDROP) - instance.show(() => { - expect(getElement().parentElement).toEqual(wrapper) - resolve() - }) - }) - }) - }) - - describe('ClassName', () => { - it('should allow configuring className', () => { - return new Promise(resolve => { - const instance = new Backdrop({ - isVisible: true, - className: 'foo' - }) - const getElement = () => document.querySelector('.foo') - instance.show(() => { - expect(getElement()).toEqual(instance._getElement()) - instance.dispose() - resolve() - }) - }) - }) - }) - }) - }) -}) diff --git a/site/src/content/docs/guides/migration.mdx b/site/src/content/docs/guides/migration.mdx index 590ad7f8eb..1061298711 100644 --- a/site/src/content/docs/guides/migration.mdx +++ b/site/src/content/docs/guides/migration.mdx @@ -176,8 +176,8 @@ Bootstrap 6 is a major release with many breaking changes to modernize our codeb - Events: `show.bs.modal` → `show.bs.dialog`, `shown.bs.modal` → `shown.bs.dialog`, `hide.bs.modal` → `hide.bs.dialog`, `hidden.bs.modal` → `hidden.bs.dialog`, `hidePrevented.bs.modal` → `hidePrevented.bs.dialog`. - Data key: `bs.modal` → `bs.dialog` (affects `Dialog.getInstance()` and `Dialog.getOrCreateInstance()`). - CSS variables: `--modal-*` → `--dialog-*`. - - Backdrop: The `.modal-backdrop` DOM element is gone — Dialog uses the native `::backdrop` pseudo-element with `backdrop-filter: blur()` support. - - Body scroll prevention: `.modal-open` on `` → `.dialog-open` on the `` element. + - Backdrop: The `.modal-backdrop` DOM element and the legacy `util/backdrop` helper are gone — Dialog uses the native `::backdrop` pseudo-element with `backdrop-filter: blur()` support. + - Body scroll prevention: `.modal-open` on `` → `.dialog-open` on the `` element. - New variant classes: `.dialog-slide-up`, `.dialog-slide-down` (slide animations), `.dialog-instant` (no animation), `.dialog-static` (static backdrop bounce), `.dialog-nonmodal` (non-modal positioning), `.dialog-scrollable`. - Non-modal support: Set `modal: false` or `data-bs-modal="false"` for non-modal dialogs. - Dialog swapping: Triggers inside an open dialog can open a new dialog and close the current one automatically.