From 10e57c05142c047e34b86d00da80674cdb65ff04 Mon Sep 17 00:00:00 2001 From: Louis Date: Fri, 31 Jul 2026 11:21:52 +0200 Subject: [PATCH] fix: close collapsed navigation flyouts --- README.md | 4 +- netbox_utilities/__init__.py | 2 +- .../static/netbox_utilities/navigation.js | 62 ++++++++++++++++--- pyproject.toml | 2 +- 4 files changed, 59 insertions(+), 11 deletions(-) diff --git a/README.md b/README.md index fc4daa9..be4a5cd 100644 --- a/README.md +++ b/README.md @@ -32,7 +32,7 @@ Release-Tag oder ein bestimmter Commit verwendet werden: ```bash /opt/netbox/venv/bin/pip install --upgrade --force-reinstall \ - "git+https://git.mrblake.cc/MrBlake/Netbox-Utilities.git@v0.5.1" + "git+https://git.mrblake.cc/MrBlake/Netbox-Utilities.git@v0.5.2" ``` Alternativ kann hinter dem `@` die vollständige Commit-ID stehen. @@ -209,6 +209,8 @@ ein und wieder aus. Dafür gibt es kein zusätzliches Bedienmenü. Beim Ziehen wird die neue Breite nach dem Loslassen im Benutzerprofil gespeichert. Im Icon-Modus öffnen sich die Menüinhalte als seitliches Flyout; auf kleinen beziehungsweise mobilen Ansichten bleibt das normale NetBox-Menü erhalten. +Das Flyout schließt sich bei Auswahl eines Eintrags, bei erneutem Klick auf +das aktive Icon, bei einem Klick außerhalb und mit der Escape-Taste. **Zurücksetzen** stellt neben Reihenfolge und Sichtbarkeit auch die normale ausgeklappte Breite von 288 Pixeln wieder her. diff --git a/netbox_utilities/__init__.py b/netbox_utilities/__init__.py index 11dccfb..9645bad 100644 --- a/netbox_utilities/__init__.py +++ b/netbox_utilities/__init__.py @@ -1,6 +1,6 @@ from netbox.plugins import PluginConfig, get_plugin_config -__version__ = "0.5.1" +__version__ = "0.5.2" class NetBoxUtilitiesConfig(PluginConfig): diff --git a/netbox_utilities/static/netbox_utilities/navigation.js b/netbox_utilities/static/netbox_utilities/navigation.js index 8c83a14..3844dac 100644 --- a/netbox_utilities/static/netbox_utilities/navigation.js +++ b/netbox_utilities/static/netbox_utilities/navigation.js @@ -71,20 +71,40 @@ }); } + function closeDropdown(item) { + if (!item) return false; + const button = item.querySelector(':scope > button.nav-link'); + const menu = item.querySelector(':scope > .dropdown-menu'); + if (!button || !menu?.classList.contains('show')) return false; + + // Let NetBox/Bootstrap tear down its Popper instance when available. + button.click(); + if (!menu.classList.contains('show')) { + menu.style.removeProperty('--netbox-utilities-dropdown-top'); + return true; + } + + // Fallback for pages on which Bootstrap's data API is unavailable. + button.classList.remove('show'); + button.setAttribute('aria-expanded', 'false'); + menu.classList.remove('show'); + menu.style.removeProperty('--netbox-utilities-dropdown-top'); + return true; + } + + function closeDropdowns(sidebar, exceptItem = null) { + sidebar.querySelectorAll(':scope > li.nav-item.dropdown').forEach((item) => { + if (item !== exceptItem) closeDropdown(item); + }); + } + function configureCollapsedDropdowns(sidebar) { sidebar.addEventListener('show.bs.dropdown', (event) => { if (!preferences.collapsed) return; const item = event.target.closest('.nav-item.dropdown'); if (!item) return; - sidebar.querySelectorAll(':scope > li.nav-item.dropdown').forEach((otherItem) => { - if (otherItem === item) return; - const otherButton = otherItem.querySelector(':scope > button.nav-link'); - const otherMenu = otherItem.querySelector(':scope > .dropdown-menu'); - otherButton?.classList.remove('show'); - otherButton?.setAttribute('aria-expanded', 'false'); - otherMenu?.classList.remove('show'); - }); + closeDropdowns(sidebar, item); const button = item.querySelector(':scope > button.nav-link'); const menu = item.querySelector(':scope > .dropdown-menu'); @@ -92,6 +112,32 @@ const top = Math.max(8, button.getBoundingClientRect().top); menu.style.setProperty('--netbox-utilities-dropdown-top', `${top}px`); }); + + sidebar.addEventListener('click', (event) => { + if (!preferences.collapsed || !(event.target instanceof Element)) return; + const selectedOption = event.target.closest('.dropdown-menu a[href], .dropdown-menu button'); + if (selectedOption) closeDropdown(selectedOption.closest('.nav-item.dropdown')); + }); + + document.addEventListener( + 'click', + (event) => { + if (!preferences.collapsed || !(event.target instanceof Element)) return; + sidebar.querySelectorAll(':scope > li.nav-item.dropdown').forEach((item) => { + if (!item.contains(event.target)) closeDropdown(item); + }); + }, + true + ); + + document.addEventListener('keydown', (event) => { + if (!preferences.collapsed || event.key !== 'Escape') return; + const openItem = sidebar.querySelector(':scope > li.nav-item.dropdown > .dropdown-menu.show')?.closest('.nav-item'); + if (openItem && closeDropdown(openItem)) { + openItem.querySelector(':scope > button.nav-link')?.focus(); + event.preventDefault(); + } + }); } async function saveLayout(action, width) { diff --git a/pyproject.toml b/pyproject.toml index e61f023..0c66bbd 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "hatchling.build" [project] name = "netbox-utilities" -version = "0.5.1" +version = "0.5.2" description = "Navigation, tenant filtering, bulk module installation, and atomic rack reordering for NetBox 4.6" readme = "README.md" requires-python = ">=3.12"