]> git.ipfire.org Git - thirdparty/bootstrap.git/commitdiff
Tab: ignore modifier+arrow shortcuts; docs: menu Escape behavior (#42553)
authorMark Otto <markd.otto@gmail.com>
Sat, 27 Jun 2026 03:15:34 +0000 (20:15 -0700)
committerGitHub <noreply@github.com>
Sat, 27 Jun 2026 03:15:34 +0000 (20:15 -0700)
* Tab: don't hijack modifier+arrow keyboard shortcuts

_keydown preventDefault'd arrow keys regardless of modifiers, so
Alt+Left/Right (browser history nav) and similar shortcuts were
swallowed inside a tablist. Ignore the event when alt/ctrl/meta is
held. Fixes #38565.

* Docs: clarify Escape behavior in menu auto-close section

Esc closes the menu while focus is within it (regardless of autoClose)
and returns focus to the toggle. Fixes #38035.

* Bump bundlewatch size thresholds

js/src/tab.js
js/tests/unit/tab.spec.js
site/src/content/docs/components/menu.mdx

index 70375ba25d1156c33f8001cae9d1bccba207a6c8..17f051345b11a861aeb47de99cdfe46260c25fcd 100644 (file)
@@ -155,6 +155,12 @@ class Tab extends BaseComponent {
       return
     }
 
+    // Don't hijack modifier+arrow shortcuts (e.g. Alt+Left/Right for browser
+    // history navigation); only the bare keys drive tablist navigation.
+    if (event.altKey || event.ctrlKey || event.metaKey) {
+      return
+    }
+
     event.stopPropagation()// stopPropagation/preventDefault both added to support up/down keys without scrolling the page
     event.preventDefault()
 
index a3cd9b8e4cb4b71e4d695a6387504b56ce287ac1..d523c0dc34fd3037f03a1b03d1a2857f53e993b2 100644 (file)
@@ -547,6 +547,30 @@ describe('Tab', () => {
       expect(spyPrevent).not.toHaveBeenCalled()
     })
 
+    it('if arrow key is pressed with a modifier (e.g. Alt+Left), ignore it', () => {
+      fixtureEl.innerHTML = [
+        '<ul class="nav">',
+        '  <li class="nav-link" data-bs-toggle="tab"></li>',
+        '</ul>'
+      ].join('')
+
+      const tabEl = fixtureEl.querySelector('.nav-link')
+      const tab = new Tab(tabEl)
+
+      const keydown = createEvent('keydown')
+      keydown.key = 'ArrowLeft'
+      keydown.altKey = true
+      const spyStop = spyOn(Event.prototype, 'stopPropagation').and.callThrough()
+      const spyPrevent = spyOn(Event.prototype, 'preventDefault').and.callThrough()
+      const spyGet = spyOn(tab, '_getChildren')
+
+      tabEl.dispatchEvent(keydown)
+
+      expect(spyGet).not.toHaveBeenCalled()
+      expect(spyStop).not.toHaveBeenCalled()
+      expect(spyPrevent).not.toHaveBeenCalled()
+    })
+
     it('if keydown event is right/down arrow, handle it', () => {
       fixtureEl.innerHTML = [
         '<div class="nav">',
index 622d9d0472da30443376872c107eec024323dcaa..7d9f73aff9b5564d50e1c392552fb160df1c8734 100644 (file)
@@ -484,6 +484,8 @@ Use `data-bs-offset` or `data-bs-reference` to change the location of the menu.
 
 By default, the menu is closed when clicking inside or outside the menu. You can use the `autoClose` option to change this behavior of the menu.
 
+Regardless of the `autoClose` setting, pressing <kbd>Esc</kbd> while focus is within the menu (its toggle or items) closes the menu and returns focus to the toggle.
+
 <Example class="d-flex flex-wrap gap-2" code={`<div class="btn-group">
     <button class="btn-solid theme-secondary" type="button" data-bs-toggle="menu" data-bs-auto-close="true" aria-expanded="false">
       Default menu