UNPKG

@rxxuzi/gumi

Version:

Clean & minimal design system with delightful interactions

197 lines 6.84 kB
// components/accordion.ts // Gumi.js v1.0.0 - Accordion Component import { $, $$, on, trigger, addClass, removeClass, hasClass } from '../core/dom'; import { icons } from '../utils/icons'; export class Accordion { constructor(container, options = {}) { this.items = []; const el = $(container); if (!el) throw new Error('Accordion container not found'); this.container = el; this.options = { multiple: false, collapsed: true, ...options }; this.init(); } /** * Initialize accordion */ init() { // Set data attribute for multiple if (this.options.multiple) { this.container.setAttribute('data-multiple', 'true'); } // Find all accordion items this.items = Array.from(this.container.querySelectorAll('.accordion-item')); this.items.forEach((item, index) => { const header = item.querySelector('.accordion-header'); const content = item.querySelector('.accordion-content'); if (!header || !content) return; // Set ARIA attributes header.setAttribute('role', 'button'); header.setAttribute('aria-expanded', 'false'); if (!header.id) header.id = `accordion-header-${index}`; content.setAttribute('role', 'region'); content.setAttribute('aria-labelledby', header.id); // Add chevron icon if not exists if (!header.querySelector('.accordion-icon')) { const iconSpan = document.createElement('span'); iconSpan.className = 'accordion-icon'; iconSpan.innerHTML = icons.chevronDown; header.appendChild(iconSpan); } // Set initial state if (this.options.collapsed && !hasClass(item, 'active')) { removeClass(item, 'active'); header.setAttribute('aria-expanded', 'false'); } else if (hasClass(item, 'active')) { header.setAttribute('aria-expanded', 'true'); } // Add click handler on(header, 'click', () => this.toggle(index)); }); } /** * Toggle accordion item */ toggle(index) { const item = this.items[index]; if (!item) return; const isActive = hasClass(item, 'active'); if (isActive) { this.close(index); } else { this.open(index); } } /** * Open accordion item */ open(index) { const item = this.items[index]; if (!item) return; const header = item.querySelector('.accordion-header'); const content = item.querySelector('.accordion-content'); if (!header || !content) return; // Close other items if not multiple if (!this.options.multiple) { this.items.forEach((otherItem, otherIndex) => { if (otherIndex !== index && hasClass(otherItem, 'active')) { this.close(otherIndex); } }); } // Open current item addClass(item, 'active'); addClass(header, 'active'); header.setAttribute('aria-expanded', 'true'); // Dispatch event trigger(item, 'accordion-toggle', { item, open: true }); } /** * Close accordion item */ close(index) { const item = this.items[index]; if (!item) return; const header = item.querySelector('.accordion-header'); const content = item.querySelector('.accordion-content'); if (!header || !content) return; removeClass(item, 'active'); removeClass(header, 'active'); header.setAttribute('aria-expanded', 'false'); // Dispatch event trigger(item, 'accordion-toggle', { item, open: false }); } /** * Open all items */ openAll() { this.items.forEach((_, index) => this.open(index)); } /** * Close all items */ closeAll() { this.items.forEach((_, index) => this.close(index)); } /** * Get active items */ getActiveItems() { return this.items .map((item, index) => hasClass(item, 'active') ? index : -1) .filter(index => index !== -1); } /** * Enable keyboard navigation */ enableKeyboardNavigation() { this.items.forEach((item, index) => { const header = item.querySelector('.accordion-header'); if (!header) return; header.setAttribute('tabindex', '0'); on(header, 'keydown', (e) => { const event = e; switch (event.key) { case 'Enter': case ' ': event.preventDefault(); this.toggle(index); break; case 'ArrowDown': event.preventDefault(); const nextIndex = (index + 1) % this.items.length; const nextHeader = this.items[nextIndex].querySelector('.accordion-header'); nextHeader === null || nextHeader === void 0 ? void 0 : nextHeader.focus(); break; case 'ArrowUp': event.preventDefault(); const prevIndex = index === 0 ? this.items.length - 1 : index - 1; const prevHeader = this.items[prevIndex].querySelector('.accordion-header'); prevHeader === null || prevHeader === void 0 ? void 0 : prevHeader.focus(); break; } }); }); } /** * Destroy accordion instance */ destroy() { this.items.forEach(item => { const header = item.querySelector('.accordion-header'); const content = item.querySelector('.accordion-content'); if (header) { header.removeAttribute('role'); header.removeAttribute('aria-expanded'); header.removeAttribute('tabindex'); } if (content) { content.removeAttribute('role'); content.removeAttribute('aria-labelledby'); } }); } /** * Static method to initialize all accordions */ static initAll(selector = '.accordion') { const containers = $$(selector); return Array.from(containers).map(container => new Accordion(container)); } } //# sourceMappingURL=accordion.js.map