]> git.ipfire.org Git - thirdparty/bootstrap.git/commitdiff
Drop deprecated `.modal-backdrop` class, and unused `util/backdrop.js` (#42538)
authorJulien Déramond <juderamond@gmail.com>
Sat, 20 Jun 2026 20:57:31 +0000 (22:57 +0200)
committerGitHub <noreply@github.com>
Sat, 20 Jun 2026 20:57:31 +0000 (13:57 -0700)
js/src/util/backdrop.js [deleted file]
js/tests/unit/util/backdrop.spec.js [deleted file]
site/src/content/docs/guides/migration.mdx

diff --git a/js/src/util/backdrop.js b/js/src/util/backdrop.js
deleted file mode 100644 (file)
index 82b5490..0000000
+++ /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 (file)
index 0faaac6..0000000
+++ /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 = '<div id="wrapper"></div>'
-
-        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 = '<div id="wrapper"></div>'
-
-            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()
-            })
-          })
-        })
-      })
-    })
-  })
-})
index 590ad7f8eb9fab39da31379fa8353661c33fc148..106129871151477e7f9d847e1e04665273c48b05 100644 (file)
@@ -176,8 +176,8 @@ Bootstrap 6 is a major release with many breaking changes to modernize our codeb
   - Events: `show.bs.modal` &rarr; `show.bs.dialog`, `shown.bs.modal` &rarr; `shown.bs.dialog`, `hide.bs.modal` &rarr; `hide.bs.dialog`, `hidden.bs.modal` &rarr; `hidden.bs.dialog`, `hidePrevented.bs.modal` &rarr; `hidePrevented.bs.dialog`.
   - Data key: `bs.modal` &rarr; `bs.dialog` (affects `Dialog.getInstance()` and `Dialog.getOrCreateInstance()`).
   - CSS variables: `--modal-*` &rarr; `--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 `<body>` &rarr; `.dialog-open` on the `<html>` 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 `<body>` &rarr; `.dialog-open` on the `<body>` 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.