This commit is contained in:
Nathan Sarang-Walters 2025-05-01 12:00:22 -07:00 committed by GitHub
commit 2b8f3f31d4
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
8 changed files with 63 additions and 72 deletions

View File

@ -34,7 +34,7 @@
}, },
{ {
"path": "./dist/js/bootstrap.bundle.js", "path": "./dist/js/bootstrap.bundle.js",
"maxSize": "43.0 kB" "maxSize": "43.25 kB"
}, },
{ {
"path": "./dist/js/bootstrap.bundle.min.js", "path": "./dist/js/bootstrap.bundle.min.js",
@ -42,7 +42,7 @@
}, },
{ {
"path": "./dist/js/bootstrap.esm.js", "path": "./dist/js/bootstrap.esm.js",
"maxSize": "28.0 kB" "maxSize": "28.25 kB"
}, },
{ {
"path": "./dist/js/bootstrap.esm.min.js", "path": "./dist/js/bootstrap.esm.min.js",
@ -54,7 +54,7 @@
}, },
{ {
"path": "./dist/js/bootstrap.min.js", "path": "./dist/js/bootstrap.min.js",
"maxSize": "16.25 kB" "maxSize": "16.5 kB"
} }
], ],
"ci": { "ci": {

View File

@ -139,7 +139,7 @@ function normalizeParameters(originalTypeEvent, handler, delegationFunction) {
return [isDelegated, callable, typeEvent] return [isDelegated, callable, typeEvent]
} }
function addHandler(element, originalTypeEvent, handler, delegationFunction, oneOff) { function addHandler(element, originalTypeEvent, handler, delegationFunction, options) {
if (typeof originalTypeEvent !== 'string' || !element) { if (typeof originalTypeEvent !== 'string' || !element) {
return return
} }
@ -165,7 +165,7 @@ function addHandler(element, originalTypeEvent, handler, delegationFunction, one
const previousFunction = findHandler(handlers, callable, isDelegated ? handler : null) const previousFunction = findHandler(handlers, callable, isDelegated ? handler : null)
if (previousFunction) { if (previousFunction) {
previousFunction.oneOff = previousFunction.oneOff && oneOff previousFunction.oneOff = previousFunction.oneOff && options.oneOff
return return
} }
@ -177,21 +177,22 @@ function addHandler(element, originalTypeEvent, handler, delegationFunction, one
fn.delegationSelector = isDelegated ? handler : null fn.delegationSelector = isDelegated ? handler : null
fn.callable = callable fn.callable = callable
fn.oneOff = oneOff fn.oneOff = options.oneOff
fn.uidEvent = uid fn.uidEvent = uid
handlers[uid] = fn handlers[uid] = fn
element.addEventListener(typeEvent, fn, isDelegated) element.addEventListener(typeEvent, fn, options?.capture ?? false)
} }
function removeHandler(element, events, typeEvent, handler, delegationSelector) { // eslint-disable-next-line max-params
function removeHandler(element, events, typeEvent, handler, delegationSelector, options) {
const fn = findHandler(events[typeEvent], handler, delegationSelector) const fn = findHandler(events[typeEvent], handler, delegationSelector)
if (!fn) { if (!fn) {
return return
} }
element.removeEventListener(typeEvent, fn, Boolean(delegationSelector)) element.removeEventListener(typeEvent, fn, options?.capture ?? false)
delete events[typeEvent][fn.uidEvent] delete events[typeEvent][fn.uidEvent]
} }
@ -212,15 +213,15 @@ function getTypeEvent(event) {
} }
const EventHandler = { const EventHandler = {
on(element, event, handler, delegationFunction) { on(element, event, handler, delegationFunction, options) {
addHandler(element, event, handler, delegationFunction, false) addHandler(element, event, handler, delegationFunction, { ...options, oneOff: false })
}, },
one(element, event, handler, delegationFunction) { one(element, event, handler, delegationFunction, options) {
addHandler(element, event, handler, delegationFunction, true) addHandler(element, event, handler, delegationFunction, { ...options, oneOff: true })
}, },
off(element, originalTypeEvent, handler, delegationFunction) { off(element, originalTypeEvent, handler, delegationFunction, options) {
if (typeof originalTypeEvent !== 'string' || !element) { if (typeof originalTypeEvent !== 'string' || !element) {
return return
} }
@ -237,7 +238,7 @@ const EventHandler = {
return return
} }
removeHandler(element, events, typeEvent, callable, isDelegated ? handler : null) removeHandler(element, events, typeEvent, callable, isDelegated ? handler : null, options)
return return
} }
@ -251,7 +252,7 @@ const EventHandler = {
const handlerKey = keyHandlers.replace(stripUidRegex, '') const handlerKey = keyHandlers.replace(stripUidRegex, '')
if (!inNamespace || originalTypeEvent.includes(handlerKey)) { if (!inNamespace || originalTypeEvent.includes(handlerKey)) {
removeHandler(element, events, typeEvent, event.callable, event.delegationSelector) removeHandler(element, events, typeEvent, event.callable, event.delegationSelector, options)
} }
} }
}, },

View File

@ -440,14 +440,14 @@ class Dropdown extends BaseComponent {
* Data API implementation * Data API implementation
*/ */
EventHandler.on(document, EVENT_KEYDOWN_DATA_API, SELECTOR_DATA_TOGGLE, Dropdown.dataApiKeydownHandler) EventHandler.on(document, EVENT_KEYDOWN_DATA_API, SELECTOR_DATA_TOGGLE, Dropdown.dataApiKeydownHandler, { capture: true })
EventHandler.on(document, EVENT_KEYDOWN_DATA_API, SELECTOR_MENU, Dropdown.dataApiKeydownHandler) EventHandler.on(document, EVENT_KEYDOWN_DATA_API, SELECTOR_MENU, Dropdown.dataApiKeydownHandler, { capture: true })
EventHandler.on(document, EVENT_CLICK_DATA_API, Dropdown.clearMenus) EventHandler.on(document, EVENT_CLICK_DATA_API, Dropdown.clearMenus, { capture: true })
EventHandler.on(document, EVENT_KEYUP_DATA_API, Dropdown.clearMenus) EventHandler.on(document, EVENT_KEYUP_DATA_API, Dropdown.clearMenus, { capture: true })
EventHandler.on(document, EVENT_CLICK_DATA_API, SELECTOR_DATA_TOGGLE, function (event) { EventHandler.on(document, EVENT_CLICK_DATA_API, SELECTOR_DATA_TOGGLE, function (event) {
event.preventDefault() event.preventDefault()
Dropdown.getOrCreateInstance(this).toggle() Dropdown.getOrCreateInstance(this).toggle()
}) }, { capture: true })
/** /**
* jQuery * jQuery

View File

@ -514,7 +514,6 @@ describe('Collapse', () => {
describe('data-api', () => { describe('data-api', () => {
it('should prevent url change if click on nested elements', () => { it('should prevent url change if click on nested elements', () => {
return new Promise(resolve => {
fixtureEl.innerHTML = [ fixtureEl.innerHTML = [
'<a role="button" data-bs-toggle="collapse" class="collapsed" href="#collapse">', '<a role="button" data-bs-toggle="collapse" class="collapsed" href="#collapse">',
' <span id="nested"></span>', ' <span id="nested"></span>',
@ -522,20 +521,13 @@ describe('Collapse', () => {
'<div id="collapse" class="collapse"></div>' '<div id="collapse" class="collapse"></div>'
].join('') ].join('')
const triggerEl = fixtureEl.querySelector('a')
const nestedTriggerEl = fixtureEl.querySelector('#nested') const nestedTriggerEl = fixtureEl.querySelector('#nested')
const spy = spyOn(Event.prototype, 'preventDefault').and.callThrough() const spy = spyOn(Event.prototype, 'preventDefault').and.callThrough()
triggerEl.addEventListener('click', event => {
expect(event.target.isEqualNode(nestedTriggerEl)).toBeTrue()
expect(event.delegateTarget.isEqualNode(triggerEl)).toBeTrue()
expect(spy).toHaveBeenCalled()
resolve()
})
nestedTriggerEl.click() nestedTriggerEl.click()
})
expect(spy).toHaveBeenCalled()
}) })
it('should show multiple collapsed elements', () => { it('should show multiple collapsed elements', () => {

View File

@ -1361,7 +1361,7 @@ describe('Dropdown', () => {
btnDropdown.addEventListener('shown.bs.dropdown', () => { btnDropdown.addEventListener('shown.bs.dropdown', () => {
expect(btnDropdown).toHaveClass('show') expect(btnDropdown).toHaveClass('show')
const keyup = createEvent('keyup') const keyup = createEvent('keyup', { bubbles: true })
keyup.key = 'Tab' keyup.key = 'Tab'
document.dispatchEvent(keyup) document.dispatchEvent(keyup)
@ -1456,7 +1456,7 @@ describe('Dropdown', () => {
expect(triggerDropdownFirst).toHaveClass('show') expect(triggerDropdownFirst).toHaveClass('show')
expect(fixtureEl.querySelectorAll('.dropdown-menu.show')).toHaveSize(1) expect(fixtureEl.querySelectorAll('.dropdown-menu.show')).toHaveSize(1)
const keyup = createEvent('keyup') const keyup = createEvent('keyup', { bubbles: true })
keyup.key = 'Tab' keyup.key = 'Tab'
document.dispatchEvent(keyup) document.dispatchEvent(keyup)
@ -1471,7 +1471,7 @@ describe('Dropdown', () => {
expect(triggerDropdownLast).toHaveClass('show') expect(triggerDropdownLast).toHaveClass('show')
expect(fixtureEl.querySelectorAll('.dropdown-menu.show')).toHaveSize(1) expect(fixtureEl.querySelectorAll('.dropdown-menu.show')).toHaveSize(1)
const keyup = createEvent('keyup') const keyup = createEvent('keyup', { bubbles: true })
keyup.key = 'Tab' keyup.key = 'Tab'
document.dispatchEvent(keyup) document.dispatchEvent(keyup)
@ -1570,7 +1570,7 @@ describe('Dropdown', () => {
}) })
triggerDropdown.addEventListener('shown.bs.dropdown', () => { triggerDropdown.addEventListener('shown.bs.dropdown', () => {
const keydown = createEvent('keydown') const keydown = createEvent('keydown', { bubbles: true })
keydown.key = 'Escape' keydown.key = 'Escape'
triggerDropdown.dispatchEvent(keydown) triggerDropdown.dispatchEvent(keydown)
@ -1637,7 +1637,7 @@ describe('Dropdown', () => {
triggerDropdown.addEventListener('shown.bs.dropdown', () => { triggerDropdown.addEventListener('shown.bs.dropdown', () => {
input.focus() input.focus()
const keydown = createEvent('keydown') const keydown = createEvent('keydown', { bubbles: true })
keydown.key = 'ArrowUp' keydown.key = 'ArrowUp'
input.dispatchEvent(keydown) input.dispatchEvent(keydown)
@ -1671,7 +1671,7 @@ describe('Dropdown', () => {
const triggerDropdown = fixtureEl.querySelector('[data-bs-toggle="dropdown"]') const triggerDropdown = fixtureEl.querySelector('[data-bs-toggle="dropdown"]')
triggerDropdown.addEventListener('shown.bs.dropdown', () => { triggerDropdown.addEventListener('shown.bs.dropdown', () => {
const keydown = createEvent('keydown') const keydown = createEvent('keydown', { bubbles: true })
keydown.key = 'ArrowDown' keydown.key = 'ArrowDown'
triggerDropdown.dispatchEvent(keydown) triggerDropdown.dispatchEvent(keydown)
@ -1708,7 +1708,7 @@ describe('Dropdown', () => {
const triggerDropdown = fixtureEl.querySelector('[data-bs-toggle="dropdown"]') const triggerDropdown = fixtureEl.querySelector('[data-bs-toggle="dropdown"]')
triggerDropdown.addEventListener('shown.bs.dropdown', () => { triggerDropdown.addEventListener('shown.bs.dropdown', () => {
const keydown = createEvent('keydown') const keydown = createEvent('keydown', { bubbles: true })
keydown.key = 'ArrowDown' keydown.key = 'ArrowDown'
triggerDropdown.dispatchEvent(keydown) triggerDropdown.dispatchEvent(keydown)
@ -1741,7 +1741,7 @@ describe('Dropdown', () => {
const item2 = fixtureEl.querySelector('#item2') const item2 = fixtureEl.querySelector('#item2')
triggerDropdown.addEventListener('shown.bs.dropdown', () => { triggerDropdown.addEventListener('shown.bs.dropdown', () => {
const keydownArrowDown = createEvent('keydown') const keydownArrowDown = createEvent('keydown', { bubbles: true })
keydownArrowDown.key = 'ArrowDown' keydownArrowDown.key = 'ArrowDown'
triggerDropdown.dispatchEvent(keydownArrowDown) triggerDropdown.dispatchEvent(keydownArrowDown)
@ -1750,7 +1750,7 @@ describe('Dropdown', () => {
document.activeElement.dispatchEvent(keydownArrowDown) document.activeElement.dispatchEvent(keydownArrowDown)
expect(document.activeElement).toEqual(item2, 'item2 is focused') expect(document.activeElement).toEqual(item2, 'item2 is focused')
const keydownArrowUp = createEvent('keydown') const keydownArrowUp = createEvent('keydown', { bubbles: true })
keydownArrowUp.key = 'ArrowUp' keydownArrowUp.key = 'ArrowUp'
document.activeElement.dispatchEvent(keydownArrowUp) document.activeElement.dispatchEvent(keydownArrowUp)
@ -1785,7 +1785,7 @@ describe('Dropdown', () => {
}) })
}) })
const keydown = createEvent('keydown') const keydown = createEvent('keydown', { bubbles: true })
keydown.key = 'ArrowUp' keydown.key = 'ArrowUp'
triggerDropdown.dispatchEvent(keydown) triggerDropdown.dispatchEvent(keydown)
}) })
@ -1813,7 +1813,7 @@ describe('Dropdown', () => {
}) })
}) })
const keydown = createEvent('keydown') const keydown = createEvent('keydown', { bubbles: true })
keydown.key = 'ArrowDown' keydown.key = 'ArrowDown'
triggerDropdown.dispatchEvent(keydown) triggerDropdown.dispatchEvent(keydown)
}) })
@ -1840,7 +1840,7 @@ describe('Dropdown', () => {
triggerDropdown.addEventListener('shown.bs.dropdown', () => { triggerDropdown.addEventListener('shown.bs.dropdown', () => {
expect(triggerDropdown).toHaveClass('show') expect(triggerDropdown).toHaveClass('show')
input.dispatchEvent(createEvent('click')) input.dispatchEvent(createEvent('click', { bubbles: true }))
}) })
triggerDropdown.click() triggerDropdown.click()
@ -1868,7 +1868,7 @@ describe('Dropdown', () => {
triggerDropdown.addEventListener('shown.bs.dropdown', () => { triggerDropdown.addEventListener('shown.bs.dropdown', () => {
expect(triggerDropdown).toHaveClass('show') expect(triggerDropdown).toHaveClass('show')
textarea.dispatchEvent(createEvent('click')) textarea.dispatchEvent(createEvent('click', { bubbles: true }))
}) })
triggerDropdown.click() triggerDropdown.click()
@ -1895,9 +1895,7 @@ describe('Dropdown', () => {
}) })
triggerDropdown.addEventListener('shown.bs.dropdown', () => { triggerDropdown.addEventListener('shown.bs.dropdown', () => {
input.dispatchEvent(createEvent('click', { input.dispatchEvent(createEvent('click', { bubbles: true }))
bubbles: true
}))
}) })
triggerDropdown.click() triggerDropdown.click()
@ -1922,14 +1920,14 @@ describe('Dropdown', () => {
const textarea = fixtureEl.querySelector('textarea') const textarea = fixtureEl.querySelector('textarea')
const test = (eventKey, elementToDispatch) => { const test = (eventKey, elementToDispatch) => {
const event = createEvent('keydown') const event = createEvent('keydown', { bubbles: true })
event.key = eventKey event.key = eventKey
elementToDispatch.focus() elementToDispatch.focus()
elementToDispatch.dispatchEvent(event) elementToDispatch.dispatchEvent(event)
expect(document.activeElement).toEqual(elementToDispatch, `${elementToDispatch.tagName} still focused`) expect(document.activeElement).toEqual(elementToDispatch, `${elementToDispatch.tagName} still focused`)
} }
const keydownEscape = createEvent('keydown') const keydownEscape = createEvent('keydown', { bubbles: true })
keydownEscape.key = 'Escape' keydownEscape.key = 'Escape'
triggerDropdown.addEventListener('shown.bs.dropdown', () => { triggerDropdown.addEventListener('shown.bs.dropdown', () => {
@ -1985,7 +1983,7 @@ describe('Dropdown', () => {
// Key escape // Key escape
button.focus() button.focus()
// Key escape // Key escape
const keydownEscape = createEvent('keydown') const keydownEscape = createEvent('keydown', { bubbles: true })
keydownEscape.key = 'Escape' keydownEscape.key = 'Escape'
button.dispatchEvent(keydownEscape) button.dispatchEvent(keydownEscape)
@ -2351,10 +2349,10 @@ describe('Dropdown', () => {
const triggerDropdown = fixtureEl.querySelector('[data-bs-toggle="dropdown"]') const triggerDropdown = fixtureEl.querySelector('[data-bs-toggle="dropdown"]')
const dropdown = fixtureEl.querySelector('.dropdown') const dropdown = fixtureEl.querySelector('.dropdown')
const keydown = createEvent('keydown') const keydown = createEvent('keydown', { bubbles: true })
keydown.key = 'ArrowDown' keydown.key = 'ArrowDown'
const keyup = createEvent('keyup') const keyup = createEvent('keyup', { bubbles: true })
keyup.key = 'ArrowUp' keyup.key = 'ArrowUp'
const handleArrowDown = () => { const handleArrowDown = () => {

View File

@ -246,7 +246,7 @@ describe('Toast', () => {
resolve() resolve()
}) })
const mouseOverEvent = createEvent('mouseover') const mouseOverEvent = createEvent('mouseover', { bubbles: true })
toastEl.dispatchEvent(mouseOverEvent) toastEl.dispatchEvent(mouseOverEvent)
}, toast._config.delay / 2) }, toast._config.delay / 2)
@ -309,7 +309,7 @@ describe('Toast', () => {
}) })
toastEl.addEventListener('focusin', () => { toastEl.addEventListener('focusin', () => {
const mouseOutEvent = createEvent('mouseout') const mouseOutEvent = createEvent('mouseout', { bubbles: true })
toastEl.dispatchEvent(mouseOutEvent) toastEl.dispatchEvent(mouseOutEvent)
}) })
@ -323,7 +323,7 @@ describe('Toast', () => {
resolve() resolve()
}) })
const mouseOverEvent = createEvent('mouseover') const mouseOverEvent = createEvent('mouseover', { bubbles: true })
toastEl.dispatchEvent(mouseOverEvent) toastEl.dispatchEvent(mouseOverEvent)
}, toast._config.delay / 2) }, toast._config.delay / 2)
@ -362,7 +362,7 @@ describe('Toast', () => {
resolve() resolve()
}) })
const mouseOverEvent = createEvent('mouseover') const mouseOverEvent = createEvent('mouseover', { bubbles: true })
toastEl.dispatchEvent(mouseOverEvent) toastEl.dispatchEvent(mouseOverEvent)
}, toast._config.delay / 2) }, toast._config.delay / 2)
@ -392,7 +392,7 @@ describe('Toast', () => {
}) })
toastEl.addEventListener('focusin', () => { toastEl.addEventListener('focusin', () => {
const mouseOutEvent = createEvent('mouseout') const mouseOutEvent = createEvent('mouseout', { bubbles: true })
toastEl.dispatchEvent(mouseOutEvent) toastEl.dispatchEvent(mouseOutEvent)
}) })
@ -401,7 +401,7 @@ describe('Toast', () => {
resolve() resolve()
}) })
const mouseOverEvent = createEvent('mouseover') const mouseOverEvent = createEvent('mouseover', { bubbles: true })
toastEl.dispatchEvent(mouseOverEvent) toastEl.dispatchEvent(mouseOverEvent)
}, toast._config.delay / 2) }, toast._config.delay / 2)

View File

@ -39,7 +39,7 @@ describe('Plugin functions', () => {
const spyTest = spyOn(DummyClass2.prototype, 'testMethod') const spyTest = spyOn(DummyClass2.prototype, 'testMethod')
const componentWrapper = fixtureEl.querySelector('#foo') const componentWrapper = fixtureEl.querySelector('#foo')
const btnClose = fixtureEl.querySelector('[data-bs-dismiss="test"]') const btnClose = fixtureEl.querySelector('[data-bs-dismiss="test"]')
const event = createEvent('click') const event = createEvent('click', { bubbles: true })
enableDismissTrigger(DummyClass2, 'testMethod') enableDismissTrigger(DummyClass2, 'testMethod')
btnClose.dispatchEvent(event) btnClose.dispatchEvent(event)
@ -59,7 +59,7 @@ describe('Plugin functions', () => {
const spyHide = spyOn(DummyClass2.prototype, 'hide') const spyHide = spyOn(DummyClass2.prototype, 'hide')
const componentWrapper = fixtureEl.querySelector('#foo') const componentWrapper = fixtureEl.querySelector('#foo')
const btnClose = fixtureEl.querySelector('[data-bs-dismiss="test"]') const btnClose = fixtureEl.querySelector('[data-bs-dismiss="test"]')
const event = createEvent('click') const event = createEvent('click', { bubbles: true })
enableDismissTrigger(DummyClass2) enableDismissTrigger(DummyClass2)
btnClose.dispatchEvent(event) btnClose.dispatchEvent(event)
@ -77,7 +77,7 @@ describe('Plugin functions', () => {
const spy = spyOn(DummyClass2, 'getOrCreateInstance').and.callThrough() const spy = spyOn(DummyClass2, 'getOrCreateInstance').and.callThrough()
const btnClose = fixtureEl.querySelector('[data-bs-dismiss="test"]') const btnClose = fixtureEl.querySelector('[data-bs-dismiss="test"]')
const event = createEvent('click') const event = createEvent('click', { bubbles: true })
enableDismissTrigger(DummyClass2) enableDismissTrigger(DummyClass2)
btnClose.dispatchEvent(event) btnClose.dispatchEvent(event)
@ -93,7 +93,7 @@ describe('Plugin functions', () => {
].join('') ].join('')
const btnClose = fixtureEl.querySelector('[data-bs-dismiss="test"]') const btnClose = fixtureEl.querySelector('[data-bs-dismiss="test"]')
const event = createEvent('click') const event = createEvent('click', { bubbles: true })
enableDismissTrigger(DummyClass2) enableDismissTrigger(DummyClass2)
const spy = spyOn(Event.prototype, 'preventDefault').and.callThrough() const spy = spyOn(Event.prototype, 'preventDefault').and.callThrough()

View File

@ -107,7 +107,7 @@ describe('FocusTrap', () => {
first.addEventListener('focusin', focusInListener) first.addEventListener('focusin', focusInListener)
const keydown = createEvent('keydown') const keydown = createEvent('keydown', { bubbles: true })
keydown.key = 'Tab' keydown.key = 'Tab'
document.dispatchEvent(keydown) document.dispatchEvent(keydown)
@ -146,7 +146,7 @@ describe('FocusTrap', () => {
last.addEventListener('focusin', focusInListener) last.addEventListener('focusin', focusInListener)
const keydown = createEvent('keydown') const keydown = createEvent('keydown', { bubbles: true })
keydown.key = 'Tab' keydown.key = 'Tab'
keydown.shiftKey = true keydown.shiftKey = true