/** * PXM Accordion Component * * A flexible, accessible accordion component that allows users to expand/collapse content sections. * Bring your own animation library (GSAP, Anime.js, etc.) or use CSS transitions. * * Features: * - Keyboard navigation * - Arrow key navigation with wrapping (Up/Down arrows wrap to first/last item) * - Dynamic content support (items can be added/removed after initialization) * - Event-driven animation system (bring your own animation library) * - Single or multiple items can be expanded simultaneously * * Keyboard Navigation: * - `Enter` or `Space` - Toggle current item * - `ArrowUp` - Focus previous item (wraps to last item if at first) * - `ArrowDown` - Focus next item (wraps to first item if at last) * - `Home` - Focus first item * - `End` - Focus last item * * Basic Usage: * ```html * * * *

Section 1

* *
* *

Content goes here...

*
*
*
* ``` * * Dynamic Content: * ```javascript * // Items can be added/removed dynamically * const accordion = document.querySelector('pxm-accordion'); * const newItem = document.createElement('pxm-accordion-item'); * newItem.innerHTML = ` * New Section * New content * `; * accordion.appendChild(newItem); // Automatically initialized * * // Listen for changes * accordion.addEventListener('pxm:accordion:items-changed', (e) => { * console.log(`Accordion now has ${e.detail.itemCount} items`); * }); * ``` * * With GSAP Animation (via events - recommended for CDN): * ```javascript * const accordion = document.querySelector('pxm-accordion'); * * accordion.addEventListener('pxm:accordion:before-expand', (e) => { * const { content, item, index } = e.detail; * e.preventDefault(); // Take over the animation * * gsap.fromTo(content, * { height: 0, opacity: 0 }, * { height: 'auto', opacity: 1, duration: 0.3, onComplete: () => { * e.detail.complete(); // Signal animation complete * }} * ); * }); * * accordion.addEventListener('pxm:accordion:before-collapse', (e) => { * const { content, item, index } = e.detail; * e.preventDefault(); // Take over the animation * * gsap.to(content, { * height: 0, * opacity: 0, * duration: 0.3, * onComplete: () => { * e.detail.complete(); // Signal animation complete * } * }); * }); * ``` * * With CSS Transitions (default): * ```css * pxm-accordion-content { * transition: opacity 0.3s ease-in-out; * } * ``` * * Consumer Styling Examples: * ```css * /* Style the component structure *\/ * pxm-accordion { * /* Your layout styles *\/ * } * * pxm-accordion-item { * /* Your item styles *\/ * } * * /* ✅ DO: Style based on data attributes (for CSS/JS targeting) / * pxm-accordion-item[data-expanded="true"] pxm-accordion-content { * /* Expanded state styling *\/ * display: block; * opacity: 1; * } * * pxm-accordion-item[data-state="active"] { * /* Active state styling *\/ * background: #f0f0f0; * } * * pxm-accordion-item[data-disabled="true"] { * /* Disabled state styling *\/ * opacity: 0.5; * } * * /* ❌ DON'T: Style based on ARIA attributes (bad practice) *\/ * pxm-accordion-item[aria-expanded="true"] { * /* Don't use ARIA for styling *\/ * } * ``` * * With Tailwind CSS: * ```html * * * * * ``` * * SSR / Hydration Support: * ```css * /* Recommended: Set initial styles in CSS to prevent hydration flash *\/ * pxm-accordion-item:not([data-expanded="true"]) pxm-accordion-content { * display: none; * opacity: 0; * } * * pxm-accordion-item[data-expanded="true"] pxm-accordion-content { * display: block; * opacity: 1; * } * * /* Optional: Hide content during hydration *\/ * pxm-accordion:not(:defined) pxm-accordion-content { * display: none; * } * ``` * * Accessibility: * This component manages both ARIA attributes (for accessibility) and data attributes (for styling/JS). * - ARIA attributes (aria-expanded) are automatically managed for screen readers * - Data attributes (data-expanded, data-state) are provided for CSS styling and JavaScript hooks * - Additional ARIA attributes, labels, and roles should be added by the consumer as needed * * Events: * - `pxm:accordion:before-expand` - Cancelable. Fired before expansion starts. * - `pxm:accordion:after-expand` - Fired after expansion completes. * - `pxm:accordion:before-collapse` - Cancelable. Fired before collapse starts. * - `pxm:accordion:after-collapse` - Fired after collapse completes. * - `pxm:accordion:toggle` - Fired when an item is toggled. * - `pxm:accordion:items-changed` - Fired when items are dynamically added/removed. * - `pxm:accordion:state-sync` - Fired when internal state syncs with manually changed DOM attributes. * * State Synchronization: * ```javascript * // Manual data attribute changes are automatically synced * const item = document.querySelector('pxm-accordion-item'); * item.setAttribute('data-expanded', 'true'); // Component state automatically updates * item.setAttribute('data-state', 'active'); // Both data attributes should be set * * // Listen for sync events * accordion.addEventListener('pxm:accordion:state-sync', (e) => { * console.log(`Item ${e.detail.index} was ${e.detail.action}`); * // Possible actions: 'activated-from-dom', 'deactivated-from-dom' * }); * ``` */ export interface AccordionEventDetail { index: number; item: HTMLElement; content: HTMLElement; trigger: HTMLElement; complete: () => void; } export interface AccordionToggleEventDetail { index: number; item: HTMLElement; isExpanding: boolean; } declare class PxmAccordion extends HTMLElement { private config; private state; private _items?; private animationPromises; private mutationObserver?; private attributeObserver?; private itemStates; private initializedItems; private itemEventListeners; private get items(); static get observedAttributes(): string[]; constructor(); connectedCallback(): void; disconnectedCallback(): void; attributeChangedCallback(name: string, oldValue: string, newValue: string): void; /** * Set up MutationObserver to watch for dynamically added/removed items */ private observeChildChanges; /** * Set up MutationObserver to watch for data attribute changes on items */ private observeAttributeChanges; /** * Sync internal state when data attributes are manually changed */ private syncStateFromDOM; /** * Handle dynamic changes to accordion items */ private handleDynamicChanges; /** * Clean up event listeners for items that have been removed from the DOM */ private cleanupRemovedItems; /** * Save the current state of all items */ private saveItemStates; /** * Restore the state of items that still exist */ private restoreItemStates; private defaultListeners; private hasCustomAnimations; private shouldSkipDefaultAnimations; /** * Set up default CSS-based animations if no hooks are provided and no event listeners exist */ private setupDefaultAnimations; /** * Helper to check if event listeners exist (for determining if we should use default animations) */ private hasEventListener; /** * Clear all event listener tracking attributes to reset to defaults */ private clearEventListenerTracking; /** * Override addEventListener to track when animation events are added */ addEventListener(type: string, listener: EventListenerOrEventListenerObject, options?: boolean | AddEventListenerOptions): void; /** * Set up accordion items with event listeners */ private setupItems; /** * Focus the previous accordion item (wraps to last item if at first) */ private focusPreviousItem; /** * Focus the next accordion item (wraps to first item if at last) */ private focusNextItem; /** * Focus the first accordion item */ private focusFirstItem; /** * Focus the last accordion item */ private focusLastItem; /** * Update icon rotation for all items */ private updateIconRotations; /** * Update icon for a specific item */ private updateIcon; /** * Create animation promise that can be resolved externally */ private createAnimationPromise; /** * Expand an accordion item */ expandItem(index: number): Promise; /** * Collapse an accordion item */ collapseItem(index: number): Promise; /** * Toggle an accordion item's state */ toggleItem(index: number): Promise; /** * Remove default animation listeners (useful when using custom animation libraries) */ removeDefaultAnimations(): void; /** * Public API methods */ expandAll(): Promise; collapseAll(): Promise; getActiveItems(): number[]; isItemActive(index: number): boolean; /** * Clean up all event listeners for all items */ private cleanupAllEventListeners; /** * Clean up event listeners for a specific item */ private cleanupItemEventListeners; /** * Add an event listener to an item and track it for cleanup */ private addItemEventListener; } declare class PxmAccordionItem extends HTMLElement { constructor(); } declare class PxmAccordionTrigger extends HTMLElement { constructor(); } declare class PxmAccordionContent extends HTMLElement { constructor(); } export type { PxmAccordion }; export type { PxmAccordionItem }; export type { PxmAccordionTrigger }; export type { PxmAccordionContent };