/** * Services Controller * Handles category collapse/expand animations */ export class ServicesController { constructor() { this.init(); } private init(): void { this.setupCategoryToggle(); } private setupCategoryToggle(): void { document.addEventListener('click', (e) => { const categoryRow = (e.target as HTMLElement).closest('swp-category-row'); if (!categoryRow) return; const isExpanded = categoryRow.getAttribute('data-expanded') !== 'false'; const categoryId = categoryRow.getAttribute('data-category'); // Find all service rows belonging to this category const serviceRows = this.getServiceRowsForCategory(categoryRow); if (isExpanded) { // Collapse - set attribute immediately so chevron animates with rows categoryRow.setAttribute('data-expanded', 'false'); this.collapseRows(serviceRows); } else { // Expand - set attribute immediately so chevron animates with rows categoryRow.setAttribute('data-expanded', 'true'); this.expandRows(serviceRows); } }); } private getServiceRowsForCategory(categoryRow: Element): HTMLElement[] { const rows: HTMLElement[] = []; let sibling = categoryRow.nextElementSibling; while (sibling && sibling.tagName.toLowerCase() === 'swp-data-table-row') { rows.push(sibling as HTMLElement); sibling = sibling.nextElementSibling; } return rows; } private collapseRows(rows: HTMLElement[]): void { if (rows.length === 0) return; // Animate each row rows.forEach((row) => { const height = row.offsetHeight; row.style.height = `${height}px`; row.style.overflow = 'hidden'; // Force reflow row.offsetHeight; row.style.transition = 'height 0.2s ease, opacity 0.2s ease'; row.style.height = '0'; row.style.opacity = '0'; }); // After animation completes setTimeout(() => { rows.forEach(row => { row.style.display = 'none'; row.style.height = ''; row.style.opacity = ''; row.style.overflow = ''; row.style.transition = ''; }); }, 200); } private expandRows(rows: HTMLElement[]): void { rows.forEach((row, index) => { // First make visible but with 0 height row.style.display = 'grid'; row.style.height = '0'; row.style.opacity = '0'; row.style.overflow = 'hidden'; // Measure natural height row.style.height = 'auto'; const naturalHeight = row.offsetHeight; row.style.height = '0'; // Force reflow row.offsetHeight; // Animate to natural height row.style.transition = 'height 0.2s ease, opacity 0.2s ease'; row.style.height = `${naturalHeight}px`; row.style.opacity = '1'; }); // Cleanup after animation setTimeout(() => { rows.forEach(row => { row.style.height = ''; row.style.opacity = ''; row.style.overflow = ''; row.style.transition = ''; }); }, 200); } }