Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 16 additions & 6 deletions js/src/menu.ts
Original file line number Diff line number Diff line change
Expand Up @@ -277,6 +277,16 @@ class Menu extends BaseComponent {
}

override dispose(): void {
if (this._menu) {
EventHandler.off(this._menu, EVENT_KEY)
}

if ('ontouchstart' in document.documentElement) {
for (const element of document.body.children) {
EventHandler.off(element, 'mouseover', noop)
}
}

this._disposeFloating()
this._restoreMenuToOriginalParent()
this._disposeMediaQueryListeners()
Expand Down Expand Up @@ -605,21 +615,21 @@ class Menu extends BaseComponent {

protected _setupSubmenuListeners(): void {
if (this._config.submenuTrigger === 'hover' || this._config.submenuTrigger === 'both') {
EventHandler.on(this._menu, 'mouseenter', SELECTOR_SUBMENU_TOGGLE, event => {
EventHandler.on(this._menu, `mouseenter${EVENT_KEY}`, SELECTOR_SUBMENU_TOGGLE, event => {
this._onSubmenuTriggerEnter(event)
})

EventHandler.on(this._menu, 'mouseleave', SELECTOR_SUBMENU, event => {
EventHandler.on(this._menu, `mouseleave${EVENT_KEY}`, SELECTOR_SUBMENU, event => {
this._onSubmenuLeave(event)
})

EventHandler.on(this._menu, 'mousemove', event => {
EventHandler.on(this._menu, `mousemove${EVENT_KEY}`, event => {
this._trackMousePosition(event)
})
}

if (this._config.submenuTrigger === 'click' || this._config.submenuTrigger === 'both') {
EventHandler.on(this._menu, 'click', SELECTOR_SUBMENU_TOGGLE, event => {
EventHandler.on(this._menu, `click${EVENT_KEY}`, SELECTOR_SUBMENU_TOGGLE, event => {
this._onSubmenuTriggerClick(event)
})
}
Expand Down Expand Up @@ -718,7 +728,7 @@ class Menu extends BaseComponent {
const cleanup = this._createSubmenuFloating(trigger, submenu, submenuWrapper)
this._openSubmenus.set(submenu, cleanup)

EventHandler.on(submenu, 'mouseenter', () => {
EventHandler.on(submenu, `mouseenter${EVENT_KEY}`, () => {
this._cancelSubmenuCloseTimeout(submenu)
})
}
Expand All @@ -742,7 +752,7 @@ class Menu extends BaseComponent {
}

this._openSubmenus.delete(submenu)
EventHandler.off(submenu, 'mouseenter')
EventHandler.off(submenu, `mouseenter${EVENT_KEY}`)

if (trigger) {
trigger.setAttribute('aria-expanded', 'false')
Expand Down
31 changes: 31 additions & 0 deletions js/tests/unit/menu.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -1171,6 +1171,37 @@ describe('Menu', () => {
expect(menu._menu).toBeNull()
expect(menu._element).toBeNull()
expect(spy).toHaveBeenCalledWith(btnMenu, Menu.EVENT_KEY)
expect(spy).toHaveBeenCalledWith(fixtureEl.querySelector('.menu'), Menu.EVENT_KEY)
})

it('should remove mouseover listeners on document.body.children if disposed while open on touch devices', () => {
return new Promise(resolve => {
fixtureEl.innerHTML = [
'<div>',
' <button class="btn" data-bs-toggle="menu">Menu</button>',
' <div class="menu">',
' <a class="menu-item" href="#">Link</a>',
' </div>',
'</div>'
].join('')

const defaultValueOnTouchStart = document.documentElement.ontouchstart
document.documentElement.ontouchstart = noop

const btnMenu = fixtureEl.querySelector('[data-bs-toggle="menu"]')
const menu = new Menu(btnMenu)
const spy = spyOn(EventHandler, 'off').and.callThrough()

btnMenu.addEventListener('shown.bs.menu', () => {
menu.dispose()

expect(spy).toHaveBeenCalledWith(jasmine.any(Element), 'mouseover', noop)
document.documentElement.ontouchstart = defaultValueOnTouchStart
resolve()
})

menu.show()
})
})

it('should dispose menu with Floating UI', () => {
Expand Down
Loading