/** * PXM Tabs Component * * A flexible, accessible tabs component for organizing content into separate panels. * Bring your own animation library (GSAP, Anime.js, etc.) or use CSS transitions. * This component provides structure and behavior only - all styling is controlled by your CSS. * * Features: * - Keyboard navigation with arrow keys, Home/End support * - Dynamic content support (tabs can be added/removed after initialization) * - Event-driven animation system (bring your own animation library) * - State synchronization with DOM attributes * - Initial tab selection via initial attribute * * Keyboard Navigation: * - `Enter` or `Space` - Activate current tab * - `ArrowLeft` / `ArrowUp` - Focus previous tab * - `ArrowRight` / `ArrowDown` - Focus next tab * - `Home` - Focus first tab * - `End` - Focus last tab * * Basic Usage: * ```html * * * * * * * *

Panel 1 Content

*

Content for tab 1...

*
* *

Panel 2 Content

*

Content for tab 2...

*
* *

Panel 3 Content

*

Content for tab 3...

*
*
* ``` * * Dynamic Content: * ```javascript * // Tabs can be added/removed dynamically * const tabs = document.querySelector('pxm-tabs'); * const newTrigger = document.createElement('button'); * newTrigger.setAttribute('data-tab', 'new-tab'); * newTrigger.textContent = 'New Tab'; * tabs.querySelector('pxm-triggers').appendChild(newTrigger); * * const newPanel = document.createElement('pxm-panel'); * newPanel.setAttribute('data-tab', 'new-tab'); * newPanel.innerHTML = '

New content

'; * tabs.appendChild(newPanel); * * // Listen for dynamic changes * tabs.addEventListener('pxm:tabs:tabs-changed', (e) => { * console.log(`Tabs now has ${e.detail.tabCount} tabs`); * }); * ``` * * With Animation Library (via events - recommended for CDN): * ```javascript * const tabs = document.querySelector('pxm-tabs'); * * tabs.addEventListener('pxm:tabs:before-show', (e) => { * const { panel, tabName } = e.detail; * e.preventDefault(); // Take over the animation * * gsap.fromTo(panel, * { opacity: 0, y: 20 }, * { opacity: 1, y: 0, duration: 0.3, onComplete: () => { * e.detail.complete(); // Signal animation complete * }} * ); * }); * * tabs.addEventListener('pxm:tabs:before-hide', (e) => { * const { panel, tabName } = e.detail; * e.preventDefault(); // Take over the animation * * gsap.to(panel, { * opacity: 0, * y: -20, * duration: 0.3, * onComplete: () => { * e.detail.complete(); // Signal animation complete * } * }); * }); * ``` * * With CSS Transitions (default): * ```css * pxm-panel { * transition: opacity 0.3s ease-in-out, transform 0.3s ease-in-out; * } * * /* ✅ DO: Style based on data attributes *\/ * pxm-panel[data-visible="true"] { * opacity: 1; * transform: translateY(0); * } * * pxm-panel[data-visible="false"] { * opacity: 0; * transform: translateY(10px); * } * ``` * * Consumer Styling Examples: * ```css * /* Style the tabs structure *\/ * pxm-tabs { * display: block; * } * * pxm-triggers { * display: flex; * border-bottom: 1px solid #ccc; * } * * pxm-triggers button { * padding: 12px 24px; * border: none; * background: transparent; * cursor: pointer; * } * * /* ✅ DO: Style based on data attributes (for CSS/JS targeting) *\/ * pxm-triggers button[data-selected="true"] { * background: #007cba; * color: white; * } * * pxm-panel { * padding: 24px; * } * * pxm-panel[data-visible="false"] { * display: none; * } * * /* ❌ DON'T: Style based on ARIA attributes (bad practice) *\/ * /* pxm-triggers button[aria-selected="true"] { } *\/ * /* pxm-panel[aria-hidden="true"] { } *\/ * ``` * * SSR / Hydration Support: * ```css * /* Prevent hydration flash *\/ * pxm-tabs:not(:defined) pxm-panel { * display: none; * } * * /* ✅ DO: Style based on data attributes *\/ * pxm-panel[data-visible="false"] { * display: none; * opacity: 0; * } * * pxm-panel[data-visible="true"] { * display: block; * opacity: 1; * } * ``` * * Events: * - `pxm:tabs:before-show` - Cancelable. Fired before a panel is shown. * - `pxm:tabs:after-show` - Fired after a panel is completely shown. * - `pxm:tabs:before-hide` - Cancelable. Fired before a panel is hidden. * - `pxm:tabs:after-hide` - Fired after a panel is completely hidden. * - `pxm:tabs:change` - Fired when the active tab changes. * - `pxm:tabs:tabs-changed` - Fired when tabs are dynamically added/removed. * - `pxm:tabs:state-sync` - Fired when internal state syncs with manually changed DOM attributes. * * Accessibility: * This component manages only essential ARIA attributes (like aria-selected and aria-hidden for functionality). * Additional ARIA attributes, labels, and roles should be added by the consumer as needed. */ export interface TabsEventDetail { tabName: string; panel: HTMLElement; trigger: HTMLElement; complete: () => void; } export interface TabsChangeEventDetail { previousTab: string | null; activeTab: string; panel: HTMLElement; trigger: HTMLElement; } export declare class PxmTriggers extends HTMLElement { constructor(); } export declare class PxmPanel extends HTMLElement { constructor(); } /** * Public TypeScript interface for the Tabs component */ export interface PxmTabs extends HTMLElement { /** * Activate a tab by its name (data-tab value) or index */ activateTab(tab: string | number): Promise; /** * Focus the next tab trigger */ focusNextTrigger(currentIndex: number): void; /** * Focus the previous tab trigger */ focusPreviousTrigger(currentIndex: number): void; /** * Focus the first tab trigger */ focusFirstTrigger(): void; /** * Focus the last tab trigger */ focusLastTrigger(): void; /** * Get the currently active tab name */ getActiveTab(): string | null; /** * Get all available tab names */ getTabNames(): string[]; /** * Remove default animation listeners (useful when using custom animation libraries) */ removeDefaultAnimations(): void; } /** * Public TypeScript interface for the tab triggers wrapper element */ export interface PxmTriggers extends HTMLElement { } /** * Public TypeScript interface for a tab panel element */ export interface PxmPanel extends HTMLElement { }