// ============================================================================ // Details // ============================================================================ // ============================================================================ // DetailManager // ============================================================================ /** * Class for managing
elements. * * Behavior: * - Accordion mode: only one
can stay open at a time. * - Clicking outside any
closes all of them. * - Elements with class 'sidebar__accordion' are excluded from exclusive behavior. */ export class DetailManager { private details: NodeListOf; private boundHandler: (event: Event) => void; /** * Initializes the DetailManager. * Selects all
elements and attaches a single document-level listener. * Excludes sidebar accordions from the exclusive behavior. * * @param selector - CSS selector for
elements (default: "details:not(.sidebar__accordion)"). */ constructor(selector: string = "details:not(.sidebar__accordion)") { this.details = document.querySelectorAll(selector); this.boundHandler = this.handleClick.bind(this); document.addEventListener("click", this.boundHandler); } /** * Handles clicks: * - If clicking a : closes all others, keeps only that one open. * - If clicking outside any
: closes all. */ private handleClick(event: Event): void { const target = event.target as HTMLElement; const summary = target.closest("summary"); const parent = summary?.parentElement as HTMLDetailsElement | null; if (parent && parent.tagName === "DETAILS") { // Clicked a summary → close others this.details.forEach((detail) => { if (detail !== parent) detail.removeAttribute("open"); }); } else { // Clicked outside → close all this.details.forEach((detail) => detail.removeAttribute("open")); } } /** * Toggles a specific
element open or closed. */ toggle(detail: HTMLDetailsElement, open: boolean): void { if (open) detail.setAttribute("open", ""); else detail.removeAttribute("open"); } /** * Cleans up the event listener (useful for SPA cleanup). */ destroy(): void { document.removeEventListener("click", this.boundHandler); } } // /** // * Class responsible for managing
elements in a document. // * It ensures that only one
element can be open at a time. // */ // export class DetailManager { // private details: NodeListOf // /** // * Initializes the DetailManager by selecting all
elements and // * attaching event listeners. // */ // constructor() { // this.details = document.querySelectorAll('details') // if (this.details.length > 0) { // this.attachEventListeners() // } // } // /** // * Attaches a click event listener to the document to handle
// * opening. // */ // private attachEventListeners(): void { // document.addEventListener('click', this.handleDocumentClick.bind(this)) // } // /** // * Handles click events on the document and closes any
elements // * that are not the target of the click. // * @param event - The MouseEvent object from the click event. // */ // private handleDocumentClick(event: MouseEvent): void { // const targetDetail = event.target as HTMLElement // if ( // targetDetail.tagName === 'SUMMARY' && // targetDetail.parentElement?.tagName === 'DETAILS' // ) { // this.details.forEach((detail) => { // if (detail !== targetDetail.parentElement) { // detail.removeAttribute('open') // } // }) // } // } // /** // * Toggles the open state of a specified
element. // * @param detail - The
element to toggle. // * @param open - A boolean indicating whether to open or close the // *
element. // */ // toggleDetail(detail: HTMLElement, open: boolean): void { // if (open) { // detail.setAttribute('open', '') // } else { // detail.removeAttribute('open') // } // } // /** // * Detaches the event listener from the document. Useful for cleanup in // * single-page applications. // */ // detachEventListeners(): void { // document.removeEventListener('click', this.handleDocumentClick.bind(this)) // } // } // // Usage // const detailsManager = new DetailManager(); // Toggle a specific detail element // const specificDetail = document.querySelector("details#specific") as HTMLElement; // detailsManager.toggleDetail(specificDetail, true); // Open // detailsManager.toggleDetail(specificDetail, false); // Close // export default class DetailManager { // private details: NodeListOf; // constructor() { // this.details = document.querySelectorAll("details"); // this.addClickListeners(); // } // private addClickListeners(): void { // this.details.forEach(targetDetail => { // targetDetail.addEventListener("click", () => this.handleDetailClick(targetDetail)); // }); // } // private handleDetailClick(targetDetail: HTMLElement): void { // this.details.forEach(detail => { // if (detail !== targetDetail) { // detail.removeAttribute("open"); // } // }); // } // } // // Usage // const detailsManager = new DetailManager(); // // Fetch all the details element. // const details = document.querySelectorAll("details")! // // Add the onclick listeners. // details.forEach((targetDetail) => { // targetDetail.addEventListener("click", () => { // // Close all the details that are not targetDetail. // details.forEach((detail) => { // if (detail !== targetDetail) { // detail.removeAttribute("open"); // } // }); // }); // });