@rxxuzi/gumi
Version:
Clean & minimal design system with delightful interactions
197 lines • 6.84 kB
JavaScript
// 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