current state
This commit is contained in:
223
resources/js/nova.js
Normal file
223
resources/js/nova.js
Normal file
@@ -0,0 +1,223 @@
|
||||
// Nova toolbar interactions
|
||||
// - dropdown menus via [data-dropdown]
|
||||
// - mobile menu toggle via [data-mobile-toggle] + #mobileMenu
|
||||
|
||||
(function () {
|
||||
function closest(el, selector) {
|
||||
while (el && el.nodeType === 1) {
|
||||
if (el.matches(selector)) return el;
|
||||
el = el.parentElement;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
function canHover() {
|
||||
return window.matchMedia && window.matchMedia('(hover: hover) and (pointer: fine)').matches;
|
||||
}
|
||||
|
||||
function setExpanded(toggleEl, expanded) {
|
||||
if (!toggleEl) return;
|
||||
toggleEl.setAttribute('aria-expanded', expanded ? 'true' : 'false');
|
||||
}
|
||||
|
||||
function closeAllDropdowns(except) {
|
||||
var dropdowns = document.querySelectorAll('[data-dropdown]');
|
||||
dropdowns.forEach(function (dropdown) {
|
||||
if (except && dropdown === except) return;
|
||||
var menu = dropdown.querySelector('[data-dropdown-menu]');
|
||||
var toggle = dropdown.querySelector('[data-dropdown-toggle]');
|
||||
if (menu) menu.classList.add('hidden');
|
||||
setExpanded(toggle, false);
|
||||
|
||||
// Close any submenus
|
||||
dropdown.querySelectorAll('[data-submenu-menu]').forEach(function (sm) {
|
||||
sm.classList.add('hidden');
|
||||
});
|
||||
dropdown.querySelectorAll('[data-submenu-toggle]').forEach(function (st) {
|
||||
setExpanded(st, false);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function openDropdown(dropdown) {
|
||||
var menu = dropdown.querySelector('[data-dropdown-menu]');
|
||||
var toggle = dropdown.querySelector('[data-dropdown-toggle]');
|
||||
if (!menu || !toggle) return;
|
||||
closeAllDropdowns(dropdown);
|
||||
menu.classList.remove('hidden');
|
||||
setExpanded(toggle, true);
|
||||
}
|
||||
|
||||
function closeDropdown(dropdown) {
|
||||
var menu = dropdown.querySelector('[data-dropdown-menu]');
|
||||
var toggle = dropdown.querySelector('[data-dropdown-toggle]');
|
||||
if (menu) menu.classList.add('hidden');
|
||||
setExpanded(toggle, false);
|
||||
}
|
||||
|
||||
function toggleDropdown(dropdown) {
|
||||
var menu = dropdown.querySelector('[data-dropdown-menu]');
|
||||
var toggle = dropdown.querySelector('[data-dropdown-toggle]');
|
||||
if (!menu || !toggle) return;
|
||||
|
||||
var isOpen = !menu.classList.contains('hidden');
|
||||
closeAllDropdowns(isOpen ? null : dropdown);
|
||||
|
||||
if (isOpen) {
|
||||
menu.classList.add('hidden');
|
||||
setExpanded(toggle, false);
|
||||
} else {
|
||||
menu.classList.remove('hidden');
|
||||
setExpanded(toggle, true);
|
||||
}
|
||||
}
|
||||
|
||||
function getMobileMenu() {
|
||||
return document.getElementById('mobileMenu');
|
||||
}
|
||||
|
||||
function closeMobileMenu() {
|
||||
var menu = getMobileMenu();
|
||||
if (!menu) return;
|
||||
menu.classList.add('hidden');
|
||||
var toggle = document.querySelector('[data-mobile-toggle]');
|
||||
setExpanded(toggle, false);
|
||||
}
|
||||
|
||||
function toggleMobileMenu() {
|
||||
var menu = getMobileMenu();
|
||||
if (!menu) return;
|
||||
|
||||
var isOpen = !menu.classList.contains('hidden');
|
||||
if (isOpen) {
|
||||
closeMobileMenu();
|
||||
} else {
|
||||
menu.classList.remove('hidden');
|
||||
var toggle = document.querySelector('[data-mobile-toggle]');
|
||||
setExpanded(toggle, true);
|
||||
closeAllDropdowns();
|
||||
}
|
||||
}
|
||||
|
||||
document.addEventListener('click', function (e) {
|
||||
var dropdownToggle = closest(e.target, '[data-dropdown-toggle]');
|
||||
if (dropdownToggle) {
|
||||
e.preventDefault();
|
||||
var dropdown = closest(dropdownToggle, '[data-dropdown]');
|
||||
if (dropdown) toggleDropdown(dropdown);
|
||||
return;
|
||||
}
|
||||
|
||||
var mobileToggle = closest(e.target, '[data-mobile-toggle]');
|
||||
if (mobileToggle) {
|
||||
e.preventDefault();
|
||||
toggleMobileMenu();
|
||||
return;
|
||||
}
|
||||
|
||||
// Submenu toggle (touch/click fallback)
|
||||
var submenuToggle = closest(e.target, '[data-submenu-toggle]');
|
||||
if (submenuToggle) {
|
||||
if (canHover()) {
|
||||
// On desktop, submenu opens on hover via CSS.
|
||||
e.preventDefault();
|
||||
return;
|
||||
}
|
||||
|
||||
e.preventDefault();
|
||||
var submenu = closest(submenuToggle, '[data-submenu]');
|
||||
if (!submenu) return;
|
||||
var menu = submenu.querySelector('[data-submenu-menu]');
|
||||
if (!menu) return;
|
||||
|
||||
// Close other submenus within the same dropdown
|
||||
var dropdown = closest(submenuToggle, '[data-dropdown]');
|
||||
if (dropdown) {
|
||||
dropdown.querySelectorAll('[data-submenu-menu]').forEach(function (sm) {
|
||||
if (sm !== menu) sm.classList.add('hidden');
|
||||
});
|
||||
dropdown.querySelectorAll('[data-submenu-toggle]').forEach(function (st) {
|
||||
if (st !== submenuToggle) setExpanded(st, false);
|
||||
});
|
||||
}
|
||||
|
||||
var isOpen = !menu.classList.contains('hidden');
|
||||
if (isOpen) {
|
||||
menu.classList.add('hidden');
|
||||
setExpanded(submenuToggle, false);
|
||||
} else {
|
||||
menu.classList.remove('hidden');
|
||||
setExpanded(submenuToggle, true);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (!closest(e.target, '[data-dropdown]')) {
|
||||
closeAllDropdowns();
|
||||
}
|
||||
});
|
||||
|
||||
// Hover-to-open for desktop pointers
|
||||
var hoverCloseTimers = new WeakMap();
|
||||
function clearHoverTimer(dropdown) {
|
||||
var t = hoverCloseTimers.get(dropdown);
|
||||
if (t) window.clearTimeout(t);
|
||||
hoverCloseTimers.delete(dropdown);
|
||||
}
|
||||
|
||||
function scheduleClose(dropdown) {
|
||||
clearHoverTimer(dropdown);
|
||||
hoverCloseTimers.set(
|
||||
dropdown,
|
||||
window.setTimeout(function () {
|
||||
closeDropdown(dropdown);
|
||||
}, 140)
|
||||
);
|
||||
}
|
||||
|
||||
function bindHoverHandlers() {
|
||||
if (!canHover()) return;
|
||||
document.querySelectorAll('[data-dropdown]').forEach(function (dropdown) {
|
||||
dropdown.addEventListener('mouseenter', function () {
|
||||
clearHoverTimer(dropdown);
|
||||
openDropdown(dropdown);
|
||||
});
|
||||
dropdown.addEventListener('mouseleave', function () {
|
||||
scheduleClose(dropdown);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
bindHoverHandlers();
|
||||
|
||||
// Submenu hover handlers: ensure flyouts open on pointer devices
|
||||
if (canHover()) {
|
||||
document.querySelectorAll('[data-submenu]').forEach(function (group) {
|
||||
var toggle = group.querySelector('[data-submenu-toggle]');
|
||||
var menu = group.querySelector('[data-submenu-menu]');
|
||||
if (!menu) return;
|
||||
|
||||
group.addEventListener('mouseenter', function () {
|
||||
menu.classList.remove('hidden');
|
||||
if (toggle) setExpanded(toggle, true);
|
||||
});
|
||||
group.addEventListener('mouseleave', function () {
|
||||
menu.classList.add('hidden');
|
||||
if (toggle) setExpanded(toggle, false);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
document.addEventListener('keydown', function (e) {
|
||||
if (e.key !== 'Escape') return;
|
||||
closeAllDropdowns();
|
||||
closeMobileMenu();
|
||||
});
|
||||
|
||||
window.addEventListener('resize', function () {
|
||||
if (window.matchMedia('(min-width: 768px)').matches) {
|
||||
closeMobileMenu();
|
||||
}
|
||||
});
|
||||
})();
|
||||
Reference in New Issue
Block a user