overlay — Sdílená logika pro overlay komponenty (dropdown, popover) — open/close, focus trap, ESC, klik mimo

resources/js/components/overlay.js
// Overlay — sdílená open/close logika pro dropdown, popover apod.
//
// Použití:
//   const overlay = new Overlay(rootEl, {
//       onClose: () => { ... },
//       closeOnEscape: true,
//       closeOnOutsideClick: true,
//   });
//   overlay.open();
//   overlay.close();
//   overlay.toggle();

import { onOutsideClick, offOutsideClick } from './base/outside-click.js';

export class Overlay {
	constructor(rootEl, opts = {}) {
		this.root = rootEl;
		this.opts = {
			openClass: 'is-open',
			closeOnEscape: true,
			closeOnOutsideClick: true,
			onOpen: () => {},
			onClose: () => {},
			...opts,
		};
		this.isOpen = false;
		this._escHandler = (e) => {
			if (e.key === 'Escape') this.close();
		};
	}

	open() {
		if (this.isOpen) return;
		this.isOpen = true;
		this.root.classList.add(this.opts.openClass);
		this.root.setAttribute('aria-hidden', 'false');

		if (this.opts.closeOnEscape) {
			document.addEventListener('keydown', this._escHandler);
		}
		if (this.opts.closeOnOutsideClick) {
			onOutsideClick(this.root, () => this.close());
		}

		this.opts.onOpen();
	}

	close() {
		if (!this.isOpen) return;
		this.isOpen = false;
		this.root.classList.remove(this.opts.openClass);
		this.root.setAttribute('aria-hidden', 'true');

		document.removeEventListener('keydown', this._escHandler);
		offOutsideClick(this.root);

		this.opts.onClose();
	}

	toggle() {
		this.isOpen ? this.close() : this.open();
	}
}