Optimized logic in which direction menus open

This commit is contained in:
Kevin Veen-Birkenbach 2025-01-14 17:34:30 +01:00
parent 7c51ac6bbc
commit f017cacebe
2 changed files with 81 additions and 8 deletions

View File

@ -1,5 +1,6 @@
document.addEventListener('DOMContentLoaded', () => { document.addEventListener('DOMContentLoaded', () => {
const menuItems = document.querySelectorAll('.nav-item.dropdown, .dropdown-submenu'); const menuItems = document.querySelectorAll('.nav-item.dropdown');
const subMenuItems = document.querySelectorAll('.dropdown-submenu');
menuItems.forEach(item => { menuItems.forEach(item => {
let timeout; let timeout;
@ -7,7 +8,7 @@ document.addEventListener('DOMContentLoaded', () => {
// Öffnen beim Hovern // Öffnen beim Hovern
item.addEventListener('mouseenter', () => { item.addEventListener('mouseenter', () => {
clearTimeout(timeout); clearTimeout(timeout);
openMenu(item); openMenu(item, true);
}); });
// Verzögertes Schließen beim Verlassen // Verzögertes Schließen beim Verlassen
@ -21,20 +22,46 @@ document.addEventListener('DOMContentLoaded', () => {
if (item.classList.contains('open')) { if (item.classList.contains('open')) {
closeMenu(item); closeMenu(item);
} else { } else {
openMenu(item); openMenu(item, true);
}
});
});
subMenuItems.forEach(item => {
let timeout;
// Öffnen beim Hovern
item.addEventListener('mouseenter', () => {
clearTimeout(timeout);
openMenu(item, false);
});
// Verzögertes Schließen beim Verlassen
item.addEventListener('mouseleave', () => {
timeout = setTimeout(() => closeMenu(item), 500);
});
// Offen lassen beim Klicken
item.addEventListener('click', (e) => {
e.stopPropagation(); // Verhindert das Schließen von Menüs bei Klick
if (item.classList.contains('open')) {
closeMenu(item);
} else {
openMenu(item, false);
} }
}); });
}); });
// Globale Klick-Listener, um Menüs zu schließen, wenn außerhalb geklickt wird // Globale Klick-Listener, um Menüs zu schließen, wenn außerhalb geklickt wird
document.addEventListener('click', () => { document.addEventListener('click', () => {
menuItems.forEach(item => closeMenu(item)); [...menuItems, ...subMenuItems].forEach(item => closeMenu(item));
}); });
function openMenu(item) { function openMenu(item, isTopLevel) {
item.classList.add('open'); item.classList.add('open');
const submenu = item.querySelector('.dropdown-menu'); const submenu = item.querySelector('.dropdown-menu');
if (submenu) { if (submenu) {
adjustMenuPosition(submenu, item, isTopLevel);
submenu.style.display = 'block'; submenu.style.display = 'block';
submenu.style.opacity = '1'; submenu.style.opacity = '1';
submenu.style.visibility = 'visible'; submenu.style.visibility = 'visible';
@ -50,4 +77,49 @@ document.addEventListener('DOMContentLoaded', () => {
submenu.style.visibility = 'hidden'; submenu.style.visibility = 'hidden';
} }
} }
function adjustMenuPosition(submenu, parent, isTopLevel) {
const rect = submenu.getBoundingClientRect();
const parentRect = parent.getBoundingClientRect();
// Platzberechnung
const spaceAbove = parentRect.top;
const spaceBelow = window.innerHeight - parentRect.bottom;
const spaceLeft = parentRect.left;
const spaceRight = window.innerWidth - parentRect.right;
// Standardpositionierung
submenu.style.top = '';
submenu.style.bottom = '';
submenu.style.left = '';
submenu.style.right = '';
if (isTopLevel) {
// Top-Level-Menüs öffnen nur nach oben oder unten
if (spaceBelow < rect.height && spaceAbove > rect.height) {
submenu.style.top = 'auto';
submenu.style.bottom = '100%';
} else {
submenu.style.top = '100%';
submenu.style.bottom = 'auto';
}
} else {
// Submenüs öffnen in die Richtung mit mehr Platz
if (spaceRight < rect.width && spaceLeft > rect.width) {
submenu.style.left = 'auto';
submenu.style.right = '100%';
} else {
submenu.style.left = '100%';
submenu.style.right = 'auto';
}
if (spaceBelow < rect.height && spaceAbove > rect.height) {
submenu.style.top = 'auto';
submenu.style.bottom = '100%';
} else {
submenu.style.top = '0';
submenu.style.bottom = 'auto';
}
}
}
}); });

View File

@ -3,7 +3,7 @@
{% for subitem in subitems %} {% for subitem in subitems %}
{% if subitem.subitems %} {% if subitem.subitems %}
<li class="dropdown-submenu position-relative"> <li class="dropdown-submenu position-relative">
<a class="dropdown-item dropdown-toggle" title="{{ subitem.description }}"> <a class="dropdown-item dropdown-toggle" href="#" title="{{ subitem.description }}">
{% if subitem.icon is defined and subitem.icon.class is defined %} {% if subitem.icon is defined and subitem.icon.class is defined %}
<i class="{{ subitem.icon.class }}"></i> {{ subitem.name }} <i class="{{ subitem.icon.class }}"></i> {{ subitem.name }}
{% else %} {% else %}
@ -33,6 +33,7 @@
{% endif %} {% endif %}
{% endfor %} {% endfor %}
{% endmacro %} {% endmacro %}
<!-- Navigation Bar --> <!-- Navigation Bar -->
<nav class="navbar navbar-expand-lg navbar-light bg-light"> <nav class="navbar navbar-expand-lg navbar-light bg-light">
<div class="container-fluid"> <div class="container-fluid">
@ -52,7 +53,7 @@
{% else %} {% else %}
<!-- Dropdown Menu --> <!-- Dropdown Menu -->
<li class="nav-item dropdown"> <li class="nav-item dropdown">
<a class="nav-link dropdown-toggle" href="#" id="navbarDropdown{{ loop.index }}" role="button" data-bs-toggle="dropdown" aria-expanded="false"> <a class="nav-link dropdown-toggle" href="#" id="navbarDropdown{{ loop.index }}" role="button" data-bs-toggle="dropdown" data-bs-display="dynamic" aria-expanded="false">
{% if item.icon is defined and item.icon.class is defined %} {% if item.icon is defined and item.icon.class is defined %}
<i class="{{ item.icon.class }}"></i> {{ item.name }} <i class="{{ item.icon.class }}"></i> {{ item.name }}
{% else %} {% else %}