Skip to content

Instantly share code, notes, and snippets.

@bartwttewaall
Created January 16, 2024 08:59
Show Gist options
  • Save bartwttewaall/4c257ea0bf9379e25b5b975b50124f36 to your computer and use it in GitHub Desktop.
Save bartwttewaall/4c257ea0bf9379e25b5b975b50124f36 to your computer and use it in GitHub Desktop.
Toggle submenu on mouseover with debounced close on mouseout
import { debounce } from 'ts-debounce';
export function initLinksWithSubmenu(el: HTMLElement) {
// find all links with children
const links = el.querySelectorAll<HTMLElement>("li.has-children > a");
let mouseover = false;
const debouncedClose = debounce(async (el: HTMLElement) => toggle(el, false), 1500);
const toggle = (el?: HTMLElement, value?: boolean) => {
close(el, true);
const li = el?.closest("li");
if (value === undefined) li?.classList.toggle("open");
else li?.classList.toggle("open", value);
}
const close = (el?: HTMLElement, others = false) => {
links.forEach((link) => {
if (others && link === el) return;
else link.closest("li")?.classList.remove("open");
});
};
links.forEach((link) => {
const submenu = link.nextElementSibling;
// we're listening on a click on the whole element to toggle 'open'
link.addEventListener("click", event => {
event.preventDefault();
if (!mouseover) toggle(event.target as HTMLElement);
});
link.addEventListener("mouseover", (event) => {
mouseover = true;
toggle(event.target as HTMLElement, true);
});
link.addEventListener("mouseout", (event) => {
mouseover = false;
debouncedClose(event.currentTarget as HTMLElement).catch(_ => {});
});
submenu?.addEventListener("mouseover", (event) => {
mouseover = true;
debouncedClose.cancel();
toggle(event.currentTarget as HTMLElement, true);
});
submenu?.addEventListener("mouseout", (event) => {
mouseover = false;
debouncedClose(event.currentTarget as HTMLElement).catch(_ => {});
});
});
// check if we click outside the navigation and then close it
document.addEventListener("click", (event) => {
if (!(event.target as HTMLElement).closest("[data-links]")) close();
});
}
export function initLanguageSwitcher(root: HTMLElement) {
const button = root.querySelector<HTMLElement>("[data-language-button]");
const submenu = root.querySelector<HTMLElement>("[data-language-menu]");
let mouseover = false;
const debouncedClose = debounce(async () => toggle(false), 1500);
const toggle = (value?: boolean) => {
if (value === undefined) submenu?.classList.toggle("open");
else submenu?.classList.toggle("open", value);
}
button?.addEventListener("click", (event: MouseEvent) => {
event.preventDefault();
if (!mouseover) toggle();
});
button?.addEventListener("mouseover", () => {
mouseover = true;
toggle(true);
});
button?.addEventListener("mouseout", () => {
mouseover = false;
debouncedClose().catch(_ => {});
});
submenu?.addEventListener("mouseover", () => {
mouseover = true;
debouncedClose.cancel();
});
submenu?.addEventListener("mouseout", () => {
mouseover = false;
debouncedClose().catch(_ => {});
});
document.addEventListener("click", (event) => {
if (!(event.target as HTMLElement).closest("[data-language-switcher]"))
toggle(false);
});
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment