Newer
Older
gnexus-ui-kit / src / js / components / navigation-shell.js
@Eugene Sukhodolskiy Eugene Sukhodolskiy 18 hours ago 2 KB Add navigation shell component
const initializedRoots = new WeakSet();

function getDrawerForToggle(toggle, root) {
	const id = toggle.getAttribute("aria-controls");

	if(id) {
		return root.getElementById?.(id) || document.getElementById(id);
	}

	return root.querySelector?.("[data-navigation-drawer]") || document.querySelector("[data-navigation-drawer]");
}

function open(drawer, toggle) {
	if(!drawer) {
		return;
	}

	drawer.classList.add("is-open");
	drawer.setAttribute("aria-hidden", "false");
	drawer.removeAttribute("inert");
	toggle?.setAttribute("aria-expanded", "true");
	document.body.classList.add("nav-drawer-open");

	const firstControl = drawer.querySelector("[data-navigation-close], a, button, input, select, textarea");
	firstControl?.focus?.();
}

function close(drawer, toggle, shouldRestoreFocus = true) {
	if(!drawer) {
		return;
	}

	drawer.classList.remove("is-open");
	drawer.setAttribute("aria-hidden", "true");
	drawer.setAttribute("inert", "");
	toggle?.setAttribute("aria-expanded", "false");
	document.body.classList.remove("nav-drawer-open");

	if(shouldRestoreFocus) {
		toggle?.focus?.();
	}
}

function init(root = document) {
	if(initializedRoots.has(root)) {
		return;
	}

	root.addEventListener("click", event => {
		const toggle = event.target.closest("[data-navigation-toggle]");

		if(!toggle) {
			return;
		}

		const drawer = getDrawerForToggle(toggle, root);

		if(drawer?.classList.contains("is-open")) {
			close(drawer, toggle);
		} else {
			open(drawer, toggle);
		}
	});

	root.addEventListener("click", event => {
		const closeControl = event.target.closest("[data-navigation-close], [data-navigation-link], .nav-drawer .list-action");

		if(!closeControl) {
			return;
		}

		const drawer = closeControl.closest("[data-navigation-drawer]") || root.querySelector("[data-navigation-drawer]");
		const toggle = drawer?.id ? root.querySelector(`[data-navigation-toggle][aria-controls="${drawer.id}"]`) : root.querySelector("[data-navigation-toggle]");
		const shouldRestoreFocus = !closeControl.matches("[data-navigation-link]");
		close(drawer, toggle, shouldRestoreFocus);
	});

	document.addEventListener("keydown", event => {
		if(event.key !== "Escape") {
			return;
		}

		root.querySelectorAll("[data-navigation-drawer].is-open").forEach(drawer => {
			const toggle = drawer.id ? root.querySelector(`[data-navigation-toggle][aria-controls="${drawer.id}"]`) : root.querySelector("[data-navigation-toggle]");
			close(drawer, toggle);
		});
	});

	initializedRoots.add(root);
}

export default {
	init,
	open,
	close
};