Lolzteam Auto-hide Menus

Автоматически скрывает всплывающие меню через 0.2 сек, если убрать курсор с триггера или меню

// ==UserScript==
// @name         Lolzteam Auto-hide Menus
// @namespace    Lolzteam Auto-hide Menus (by Avenick: https://lolz.live/avenick/)
// @version      0.3
// @description  Автоматически скрывает всплывающие меню через 0.2 сек, если убрать курсор с триггера или меню
// @author       Avenick
// @match        *://zelenka.guru/*
// @match        *://lolz.live/*
// @match        *://lolz.guru/*
// @icon         https://www.google.com/s2/favicons?sz=64&domain=lolz.live
// @license      MIT
// @grant        none
// ==/UserScript==


// !!! ВНИМАНИЕ: я не сильно шарю за js, так что код писался с помощью chatgpt (или deepseek), вот такой вот я вайбкодер :)
// так что если найдете что улучшить, пофиксить - пишите мне в лс на форум https://lolz.live/avenick/


// тут если хотите можете поставить свою задержку закрытия меню (в миллисекундах)
const HIDE_DELAY = 200;

(function() {
    'use strict';

    // Таймер для скрытия меню
    let hideTimer = null;

    // Функция для скрытия всех открытых меню
    function hideAllMenus() {
        document.querySelectorAll('.Menu.MenuOpened').forEach(menu => {
            // Не закрываем меню, если курсор находится внутри него или его дочерних меню
            if (!menu.matches(':hover') && !menu.querySelector('.Menu.MenuOpened:hover')) {
                menu.classList.remove('MenuOpened');
            }
        });

        document.querySelectorAll('.PopupControl.PopupOpen').forEach(control => {
            // Не изменяем состояние контрола, если курсор находится внутри связанного меню
            const menuId = control.querySelector('a[rel="Menu"]')?.getAttribute('href')?.replace('#', '');
            const relatedMenu = menuId ? document.getElementById(menuId) : null;

            if (!relatedMenu?.classList.contains('MenuOpened') ||
                (!relatedMenu.matches(':hover') && !relatedMenu.querySelector('.Menu.MenuOpened:hover'))) {
                control.classList.remove('PopupOpen');
                control.classList.add('PopupClosed');
            }
        });
    }

    // Проверка нахождения элемента внутри открытого меню
    function isInsideAnyMenu(element) {
        return element?.closest('.Menu.MenuOpened') !== null;
    }

    // Проверка нахождения элемента над контролом
    function isOverControl(element) {
        return element?.closest('.PopupControl') !== null;
    }

    // Обработчик для элементов, открывающих меню
    function setupPopupControls() {
        document.querySelectorAll('.PopupControl').forEach(control => {
            // При наведении на элемент - отменяем таймер скрытия
            control.addEventListener('mouseenter', () => {
                if (hideTimer) {
                    clearTimeout(hideTimer);
                    hideTimer = null;
                }
            });

            // При уходе с элемента - запускаем таймер скрытия, только если курсор не перешёл в меню
            control.addEventListener('mouseleave', (e) => {
                if (!isInsideAnyMenu(e.relatedTarget) && !isOverControl(e.relatedTarget)) {
                    if (!hideTimer) {
                        hideTimer = setTimeout(hideAllMenus, HIDE_DELAY);
                    }
                }
            });
        });
    }

    // Обработчик для самих меню
    function setupMenus() {
        document.querySelectorAll('.Menu').forEach(menu => {
            // При наведении на меню - отменяем таймер скрытия
            menu.addEventListener('mouseenter', () => {
                if (hideTimer) {
                    clearTimeout(hideTimer);
                    hideTimer = null;
                }
            });

            // При уходе с меню - запускаем таймер скрытия, только если курсор не перешёл в другое меню или контрол
            menu.addEventListener('mouseleave', (e) => {
                if (!isInsideAnyMenu(e.relatedTarget) && !isOverControl(e.relatedTarget)) {
                    if (!hideTimer) {
                        hideTimer = setTimeout(hideAllMenus, HIDE_DELAY);
                    }
                }
            });
        });
    }

    // Инициализация
    window.addEventListener('load', () => {
        setupPopupControls();
        setupMenus();
    });

    // Обработка динамических элементов
    const observer = new MutationObserver((mutations) => {
        mutations.forEach((mutation) => {
            if (mutation.addedNodes.length) {
                setupPopupControls();
                setupMenus();
            }
        });
    });

    observer.observe(document.body, {
        childList: true,
        subtree: true
    });

    // Консольное сообщение о загрузке
    console.log(`Lolzteam Auto-hide Menus loaded (delay: ${HIDE_DELAY}ms)`);
})();