]> git.ipfire.org Git - thirdparty/bootstrap.git/commitdiff
fix(nav-overflow): support direct button nav links copilot/allow-nav-overflow-button-support 42204/head
authorcopilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Wed, 18 Mar 2026 20:14:31 +0000 (20:14 +0000)
committercopilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Wed, 18 Mar 2026 20:14:31 +0000 (20:14 +0000)
Co-authored-by: mdo <98681+mdo@users.noreply.github.com>
js/src/nav-overflow.js
js/tests/unit/nav-overflow.spec.js
site/src/content/docs/components/nav-overflow.mdx

index 7e0ef54c70254192b76e9e920f5dcd1f780ea882..3e474ac261d85a299587e41fb287764e81425c76 100644 (file)
@@ -22,6 +22,7 @@ const EVENT_OVERFLOW = `overflow${EVENT_KEY}`
 
 const CLASS_NAME_OVERFLOW = 'nav-overflow'
 const CLASS_NAME_OVERFLOW_MENU = 'nav-overflow-menu'
+const CLASS_NAME_OVERFLOW_ITEM = 'nav-overflow-item'
 const CLASS_NAME_HIDDEN = 'd-none'
 
 const SELECTOR_NAV_ITEM = '.nav-item'
@@ -100,8 +101,16 @@ class NavOverflow extends BaseComponent {
     // Add overflow class to nav
     this._element.classList.add(CLASS_NAME_OVERFLOW)
 
-    // Get all nav items
-    this._items = [...SelectorEngine.find(SELECTOR_NAV_ITEM, this._element)]
+    // Get all supported nav items from direct children:
+    // - .nav-item containers
+    // - direct .nav-link elements (e.g. button-based tabs)
+    this._items = [...this._element.children].filter(item => {
+      if (item.classList.contains(CLASS_NAME_OVERFLOW_ITEM) || item.classList.contains(CLASS_NAME_OVERFLOW_MENU)) {
+        return false
+      }
+
+      return item.matches(SELECTOR_NAV_ITEM) || item.matches(`${SELECTOR_NAV_LINK}:not(${SELECTOR_OVERFLOW_TOGGLE})`)
+    })
 
     // Store original order data
     for (const [index, item] of this._items.entries()) {
@@ -227,7 +236,7 @@ class NavOverflow extends BaseComponent {
 
     for (const item of items) {
       // Clone the nav link as a dropdown item
-      const link = SelectorEngine.findOne(SELECTOR_NAV_LINK, item)
+      const link = item.matches(SELECTOR_NAV_LINK) ? item : SelectorEngine.findOne(SELECTOR_NAV_LINK, item)
       if (!link) {
         continue
       }
index 2a93da749f899ea823c9b72a2a0461243dbbc02f..db0d109feee4398d3e999b95b1d240bd07f8c657 100644 (file)
@@ -270,6 +270,29 @@ describe('NavOverflow', () => {
       navOverflow.dispose()
     })
 
+    it('should overflow direct button nav links', () => {
+      fixtureEl.innerHTML = [
+        '<div style="width: 250px;">',
+        '  <div class="nav nav-tabs" style="display: flex; width: 250px;" data-bs-toggle="nav-overflow">',
+        '    <button class="nav-link active" type="button" style="flex: 0 0 100px; width: 100px;">Tab 1</button>',
+        '    <button class="nav-link" type="button" style="flex: 0 0 100px; width: 100px;">Tab 2</button>',
+        '    <button class="nav-link" type="button" style="flex: 0 0 100px; width: 100px;">Tab 3</button>',
+        '    <button class="nav-link" type="button" style="flex: 0 0 100px; width: 100px;">Tab 4</button>',
+        '  </div>',
+        '</div>'
+      ].join('')
+
+      const navEl = fixtureEl.querySelector('[data-bs-toggle="nav-overflow"]')
+      const navOverflow = new NavOverflow(navEl)
+      const hiddenItems = navEl.querySelectorAll('.nav-link[data-bs-nav-overflow="true"]')
+      const dropdownButtons = navEl.querySelectorAll('.nav-overflow-menu button.dropdown-item')
+
+      expect(hiddenItems.length).toBeGreaterThan(0)
+      expect(dropdownButtons.length).toEqual(hiddenItems.length)
+
+      navOverflow.dispose()
+    })
+
     it('should show overflow toggle when items overflow', () => {
       fixtureEl.innerHTML = [
         '<ul class="nav" style="display: flex; width: 250px;" data-bs-toggle="nav-overflow">',
index 542e27efa146877ba5ba96971131893b585edde2..b56a577cc5a41cd3e25afe92b0d5cdf098ae8d3e 100644 (file)
@@ -14,6 +14,7 @@ Here's what you need to know before getting started:
 - **Responds to container size**, not viewport size. The component uses a ResizeObserver to monitor its own width, so it works perfectly in embedded contexts, documentation examples, and responsive containers.
 - Overflow items are cloned into a "More" dropdown menu while the originals are hidden.
 - Works with all nav styles: default, pills, tabs, and underline.
+- Supports both `.nav-item > .nav-link` markup and direct `.nav-link` children (including button-based tabs).
 - Active and disabled states are preserved in the overflow menu.
 
 <Callout name="info-prefersreducedmotion" />