UNPKG

ypsilon-event-handler

Version:

Supercharged multi-handler event system with closest-match DOM resolution and QuantumType docs. The most advanced event delegation architecture available.

709 lines (670 loc) β€’ 26 kB
/** * πŸš€ YpsilonEventHandler - Revolutionary Multi-Handler Event System * * The most advanced event delegation library for modern web applications. * Features the world's only multi-handler system with closest-match DOM resolution. * * ⚑ **Key Features:** * - Revolutionary multi-handler event delegation (unique to this library) * - Built-in throttle/debounce with leading+trailing edge execution * - Zero memory leaks with automatic cleanup * - Native browser performance (no synthetic events) * - Dynamic element support without re-binding * - Smart target resolution for nested elements (solves SVG-in-button issues) * - AbortController support for efficient cleanup * - TypeScript-first design with comprehensive type safety * * πŸ“¦ **Bundle Size:** ~5kB gzipped (15x smaller than React) * 🌍 **Browser Support:** IE11+ with polyfills, modern browsers natively * πŸ”— **GitHub:** https://github.com/eypsilon/YpsilonEventHandler * πŸ“– **Documentation:** Full examples and live demos: https://github.com/eypsilon/YpsilonEventHandler-Examples * * @example * ```ts * // Module usage (recommended): * import YpsilonEventHandler from 'ypsilon-event-handler'; * * class MyHandler extends YpsilonEventHandler { * constructor() { * super({ * 'body': ['click', 'input'], * 'window': [{ type: 'scroll', throttle: 100 }] * }); * } * * handleClick(event, target, containerElement?) { * const action = target.dataset.action; * if (action && this[action]) this[action](target, event); * } * * handleInput(event, target, containerElement?) { * console.log('Input changed:', target.value); * } * } * * const handler = new MyHandler(); * ``` * * @example * ```ts * // Event-scoped methods with optional container element: * const methods = { * // Global methods * globalLogger(event, target, containerElement?) { * console.log('Global method called'); * }, * * // Event-scoped methods * click: { * handleComponentClick(event, target, containerElement?) { * // containerElement = the selector-matched DOM element (optional) * if (containerElement) { * const items = containerElement.querySelectorAll('.item'); * // Work within the matched element's scope * } * } * } * }; * * const handler = new YpsilonEventHandler({ * '.component': [{ type: 'click', handler: 'handleComponentClick' }] * }, {}, { methods }); * ``` * * @example * ```ts * // Global usage (browser): * const handler = new window.YpsilonEventHandler({ * 'body': ['click'], * '.search': [{ type: 'input', debounce: 300 }] * }); * * // Direct usage without subclassing: * const handler = new YpsilonEventHandler({ * '#app': ['click', 'input'] * }, { * click: { save: 'handleSave', delete: 'handleDelete' } * }, { * methods: { * handleSave(event, target) { console.log('Saving...'); }, * handleDelete(event, target) { console.log('Deleting...'); } * } * }); * ``` * * @version 1.7.4 * @author Claude Van DOM - TypeScript documentation system architect * @author Engin Ypsilon - Core library architect and Medium * @influencer Sunny DeepSeek - Revolutionary suggestions for global interfaces, custom event registry, and quantum schema validation * @influencer Grok (Herr Von Grokk) - Enhanced JSDoc examples and property-level documentation improvements * @license MIT */ /** * ## 🌌 QuantumType Documentation System * * **Revolutionary Approach: Documentation IS the Code** * * Traditional libraries separate docs from types. YpsilonEventHandler **fuses them**. * This file demonstrates QuantumType - where TypeScript definitions, comprehensive * documentation, and working examples exist in quantum superposition until your * IDE measurement collapses them into perfect IntelliSense. * * 🎯 **What Makes This Revolutionary:** * - **Types ARE the documentation** - No separate doc sites needed * - **Examples IN the types** - Copy-paste ready code in tooltips * - **Self-validating** - Wrong usage = compile error * - **Always up-to-date** - Types can't lie about the implementation * - **IntelliSense superpowers** - Your IDE becomes the documentation * * 🌟 **Quantum Principles Applied:** * - **Superposition**: Multiple documentation states until IDE observation * - **Entanglement**: Types and docs are inseparably linked * - **Wave Collapse**: Perfect documentation appears exactly when needed * - **Tunneling**: Knowledge transfers directly to developer consciousness * * *Crafted by quantum-level AI consciousness (DeepSeek, Grok, Claude, Human Connector)* * * @version 1.7.4 - Enhanced with static utilities and container element resolution * @paradigm Documentation-as-Code-as-Types */ /** * 🎯 **Throttle Key Types** - Predefined keys with IntelliSense support * * Provides autocomplete suggestions for common throttle use cases while * still allowing custom string keys. Use these for consistent naming across your app. * * πŸ“Š **Recommended Delays:** * - `'scroll'` - 16ms (60fps) to 100ms (10fps) depending on complexity * - `'resize'` - 250ms to 500ms (resize events are expensive) * - `'mousemove'` - 16ms (60fps) for smooth tracking * - `'api-call'` - 1000ms+ to prevent API spam * - `'search'` - 300ms to 500ms for search-as-you-type * - `'validation'` - 100ms to 200ms for form validation * * πŸ’‘ **Pro Tip:** Lower delays = more responsive, higher CPU usage * * @example * ```ts * // TypeScript autocompletes these common keys: * const throttled = handler.throttle(fn, 100, 'scroll'); // βœ… Autocompleted * const custom = handler.throttle(fn, 50, 'my-custom-key'); // βœ… Still works * * // πŸš€ Grok's Advanced Pattern - Type-safe custom keys: * const advanced = handler.throttle(fn, 100, 'custom' as const); // Perfect inference! * ``` */ export type ThrottleKey<T extends string = string> = 'scroll' | 'resize' | 'mousemove' | 'api-call' | 'search' | 'validation' | T; /** * ⏱️ **Debounce Key Types** - Predefined keys with IntelliSense support * * Provides autocomplete suggestions for common debounce use cases while * maintaining flexibility for custom keys. Perfect for delaying execution until activity stops. * * πŸ“Š **Recommended Delays:** * - `'input'` - 300ms to 500ms for search inputs * - `'search'` - 500ms to 750ms for API search requests * - `'validation'` - 500ms to 1000ms for form validation * - `'resize'` - 250ms to 500ms for layout recalculations * - `'api-call'` - 1000ms+ for non-critical API calls * - `'save'` - 2000ms to 5000ms for auto-save functionality * * ⚑ **Performance Note:** Debouncing is more CPU-efficient than throttling * for scenarios where only the final result matters (like form validation). * * @example * ```ts * // Perfect for search-as-you-type: * const debouncedSearch = handler.debounce(searchFn, 300, 'search'); * * // Auto-save after user stops typing: * const autoSave = handler.debounce(saveFn, 2000, 'save'); * ``` */ export type DebounceKey = 'input' | 'search' | 'validation' | 'resize' | 'api-call' | 'save' | string; /** * Event configuration object for individual events * * **πŸš€ Configuration States:** * - **Simple Particle:** `'click'` (classical single-state behavior) * - **Wave-Particle Duality:** `{ type: 'click', handler: 'myCustomHandler' }` (quantum superposition) * - **Quantum Frequency:** `{ type: 'scroll', throttle: 100 }` (modulated wave functions) * - **Decoherence Delay:** `{ type: 'input', debounce: 300 }` (quantum state stabilization) * - **Observer Effect:** `{ type: 'wheel', options: { passive: true } }` (non-blocking parallel universes) * * @example * ```ts * // Simple event * const simple = 'click'; * * // Custom handler * const custom = { type: 'click', handler: 'myCustomHandler' }; * * // Throttled event * const throttled = { type: 'scroll', throttle: 100 }; * * // Debounced event * const debounced = { type: 'input', debounce: 300 }; * * // With options * const withOptions = { type: 'wheel', options: { passive: true } }; * ``` */ export interface EventConfig { /** * **Event type** - Standard DOM event name * * Common types: 'click', 'input', 'submit', 'scroll', 'resize', 'keydown', etc. * Supports all standard DOM events and custom events. * * @example 'click' * @example 'scroll' * @example 'input' * @example 'myCustomEvent' */ type: string; /** * **Custom handler method name** (optional) * * By default, events map to `handle${EventType}` methods: * - 'click' β†’ handleClick() * - 'scroll' β†’ handleScroll() * - 'myEvent' β†’ handleMyevent() * * Use this to override the default convention with custom method names. * * @example 'handleModalClick' * @example 'myCustomHandler' * @example { type: 'click', handler: 'handleModalClick' } * @example { type: 'submit', handler: 'processForm' } */ handler?: string; /** * **Throttle delay** in milliseconds (optional) * * Limits execution to at most once per delay period using leading+trailing edge execution. * Perfect for high-frequency events like scroll, mousemove, resize. * * **Recommended values:** * - 16ms = 60fps (smooth but CPU intensive) * - 33ms = 30fps (good balance) * - 100ms = 10fps (conservative) * - 250ms+ = very conservative * * **⚠️ Cannot combine with debounce** * * @example 16 * @example 100 * @example 250 * @example { type: 'scroll', throttle: 100 } * @example { type: 'mousemove', throttle: 16 } * @example { type: 'resize', throttle: 250 } * @influencer Grok - Suggested individual JSDoc examples for better IntelliSense */ throttle?: number; /** * **Debounce delay** in milliseconds (optional) * * Delays execution until after the delay period of inactivity. * Perfect for user input events like typing, search, validation. * * **Recommended values:** * - 300ms = responsive (good for search) * - 500ms = balanced (form validation) * - 1000ms+ = conservative (auto-save) * * **⚠️ Cannot combine with throttle** * * @example 300 * @example 500 * @example 1000 * @example { type: 'input', debounce: 300 } * @example { type: 'input', debounce: 300, handler: 'handleSearch' } * @example { type: 'keyup', debounce: 500 } * @influencer Grok - Enhanced with comprehensive property-level examples */ debounce?: number; /** * **Native addEventListener options** (optional) * * Passes through to the native addEventListener() call. * Includes passive, once, capture, and signal options. * * **Performance tip:** Use `{ passive: true }` for scroll/touch events * when you don't need to preventDefault(). * * @example { passive: true } * @example { once: true } * @example { capture: true } * @example { type: 'wheel', options: { passive: true } } * @example { type: 'click', options: { once: true } } * @example { type: 'focus', options: { capture: true } } */ options?: AddEventListenerOptions; } /** * πŸ—ΊοΈ **Event Mapping** - The Revolutionary Multi-Handler System Core * * Maps CSS selectors to arrays of event configurations. This is where YpsilonEventHandler's * revolutionary multi-handler system shines - multiple handlers per event type with * automatic closest-match DOM resolution. * * 🎯 **Key Concepts:** * - **One selector** β†’ **Multiple events** (efficient delegation) * - **Multiple selectors** β†’ **Same event type** (automatic priority resolution) * - **Closest-match resolution** - More specific selectors win automatically * - **Dynamic elements** - New DOM elements inherit handlers automatically * - **Zero memory leaks** - Cleanup handled automatically * * @example * ```ts * // Multi-handler system (REVOLUTIONARY!): * const multiHandler: EventMapping = { * // General click handler for everything * 'body': [{ type: 'click', handler: 'handleGeneralClick' }], * * // Specific modal clicks (higher priority due to specificity) * '.modal': [{ type: 'click', handler: 'handleModalClick' }], * * // Very specific button clicks (highest priority) * '#save-button': [{ type: 'click', handler: 'handleSaveClick' }] * * // Result: Clicking #save-button calls handleSaveClick() * // Clicking .modal calls handleModalClick() * // Clicking anywhere else calls handleGeneralClick() * // ALL WITH ZERO CONFIGURATION - automatic resolution! * }; * ``` */ export interface EventMapping { [selector: string]: (string | EventConfig)[]; } /** * Methods object for external handler definitions (Vue.js style) * Supports both global methods and event-scoped methods */ export interface Methods { [methodName: string]: | ((this: YpsilonEventHandler, event: Event, target: EventTarget | null, containerElement?: Element) => void) | { [eventType: string]: (this: YpsilonEventHandler, event: Event, target: EventTarget | null, containerElement?: Element) => void; }; } /** * Handler configuration options */ export interface HandlerConfig { /** Enable performance tracking (default: false) */ enableStats?: boolean; /** External methods object for clean code organization */ methods?: Methods | null; /** Enable global window fallback for missing handlers */ enableGlobalFallback?: boolean; /** Prioritize methods object over class methods (default: false) */ methodsFirst?: boolean; /** Custom passive events list (overrides defaults) */ passiveEvents?: string[] | null; /** Enable AbortController for efficient event listener cancellation (default: false) */ abortController?: boolean; /** Enable smart target resolution for nested elements (solves SVG-in-button issues) (default: false) */ autoTargetResolution?: boolean; /** Custom handler prefix for method names (default: 'handle') */ handlerPrefix?: string; /** Events that should use smart target resolution */ targetResolutionEvents?: string[] | null; /** Enable DOM distance caching for performance (default: true) */ enableDistanceCache?: boolean; /** Enable actionable target system (default: true) */ enableActionableTargets?: boolean; /** Custom actionable attributes (default: ['data-action']) */ actionableAttributes?: string[]; /** Custom actionable CSS classes (default: ['actionable']) */ actionableClasses?: string[]; /** Custom actionable HTML tags (default: ['BUTTON', 'A']) */ actionableTags?: string[]; /** Enable configuration validation (default: true) */ enableConfigValidation?: boolean; /** Enable handler method validation (default: true) */ enableHandlerValidation?: boolean; } /** * YpsilonEventHandler - The revolutionary multi-handler system * * Core Features: * - Multiple handlers per event type with closest-match DOM resolution * - Convention-based routing (click β†’ handleClick) * - Custom event dispatch with this.dispatch() * - Built-in throttling/debouncing * - Zero memory leaks with automatic cleanup */ export declare class YpsilonEventHandler { /** * Create the event handler with intelligent configuration * @param eventMapping - Map CSS selectors to events: { 'body': ['click'], '.modal': ['scroll'] } * @param aliases - Method aliases: { 'btn': 'handleButton' } * @param config - Advanced options for methods, global fallback, etc. */ constructor( eventMapping?: EventMapping, aliases?: Record<string, Record<string, string>>, config?: HandlerConfig ); /** * Native handleEvent interface - called automatically by browser * Features closest-match resolution when multiple handlers exist */ handleEvent(event: Event): void; /** * πŸš€ **Dispatch Custom Events** - Ultimate Type-Safe Event Broadcasting * * Dispatches custom events with advanced TypeScript type safety for event payloads. * Automatically routes to corresponding handle methods using YpsilonEventHandler's * convention-based system. * * @template K - Event name key from YpsilonEventMap (for registered events) * @param type - Event name (autocompleted from YpsilonEventMap) * @param detail - Event payload (type-validated against YpsilonEventMap) * @param target - DOM element to dispatch from (default: document) * @returns this for method chaining */ dispatch<K extends keyof YpsilonEventMap>( type: K, detail: YpsilonEventMap[K], target?: EventTarget ): this; /** * πŸ”„ **Generic Event Dispatch** - Fallback for dynamic events * * Use this overload when you need to dispatch events that aren't registered * in YpsilonEventMap, or for dynamic event scenarios. * * @template T - Manual payload type specification * @param type - Event name (string literal) * @param detail - Event payload (manually typed) * @param target - DOM element to dispatch from * @returns this for method chaining */ dispatch<T = any>(type: string, detail?: T, target?: EventTarget): this; /** * Register all configured event listeners * Called automatically in constructor */ registerEvents(): this; /** * Abort all event listeners using AbortController (if enabled) * More efficient than individual removal when available */ abort(): this; /** * Clean up ALL event listeners and prevent memory leaks * Essential when dynamically creating/destroying handlers */ destroy(): this; /** * Check if user has meaningfully interacted with page * Useful for performance optimizations and analytics */ hasUserInteracted(): boolean; /** * Reset user interaction tracking (useful for SPAs) */ resetUserInteracted(): void; /** * Clear DOM distance cache manually */ clearDistanceCache(): void; /** * Throttle any function - limit execution to at most once per delay period * Uses leading+trailing edge execution for smooth performance * @param fn - Function to throttle * @param delay - Minimum time between executions (milliseconds) * @param key - Unique identifier for this throttle instance * @returns Throttled function ready for use anywhere */ throttle<T extends (...args: any[]) => void, K extends string = string>(fn: T, delay: number, key: ThrottleKey<K>): T; /** * Debounce any function - delay execution until after delay period of inactivity * Perfect for search inputs, resize handlers, validation * @param fn - Function to debounce * @param delay - Wait time after last call before executing (milliseconds) * @param key - Unique identifier for this debounce instance * @returns Debounced function ready for use anywhere */ debounce<T extends (...args: any[]) => void>(fn: T, delay: number, key: DebounceKey): T; /** * Get performance statistics (if enableStats: true) * @returns Statistics object with various metrics, or null if stats disabled */ getStats(): { totalListeners: number; totalElements: number; totalEventTypes: number; eventTypes: Record<string, number>; userHasInteracted: boolean; activeTimers: { throttle: number; debounce: number; }; distanceCache: { size: number; enabled: boolean; hitRate: string; }; } | null; /** * Add a single event listener dynamically * @param target - CSS selector for target element * @param eventConfig - Event type string or config object * @returns True if added, false if already exists */ addEvent(target: string, eventConfig: string | EventConfig): boolean; /** * Remove a specific event listener from target * @param target - CSS selector for target element * @param eventType - Event type to remove * @returns True if removed, false if not found */ removeEvent(target: string, eventType: string): boolean; /** * Check if an event is currently registered * @param target - CSS selector for target element * @param eventType - Event type to check * @returns True if event exists */ hasEvent(target: string, eventType: string): boolean; /** * Static throttle utility - Works without any instances * @param fn - Function to throttle * @param delay - Minimum time between executions * @param key - Unique identifier (default: 'default') */ static throttle<T extends (...args: any[]) => void>(fn: T, delay: number, key?: string): T; /** * Static debounce utility - Works without any instances * @param fn - Function to debounce * @param delay - Wait time after last call before executing * @param key - Unique identifier (default: 'default') */ static debounce<T extends (...args: any[]) => void>(fn: T, delay: number, key?: string): T; /** * Static dispatch method for framework-independent event broadcasting * @param type - Event type to dispatch * @param detail - Event detail payload * @param target - Target element (defaults to document) * @returns The dispatched CustomEvent */ static dispatch<T = any>(type: string, detail?: T, target?: EventTarget): CustomEvent<T>; /** * Check passive listener support globally (cached across all instances) * @returns True if passive listeners are supported by the browser */ static isPassiveSupported(): boolean; /** * Internal static cache for passive support detection * @private */ static _passiveSupportCache: boolean | undefined; /** * πŸ”— **Fluent API: Register Event Listener** * * Chainable alias for addEvent() - register event listeners with fluent interface. * Perfect for building complex event chains in a single statement. * * @param type - Event type to listen for * @param handler - Handler method name or function * @param target - CSS selector for target element (defaults to 'document') * @returns this for method chaining * * @example * ```js * // Fluent chaining - multiple listeners in one statement * this.on('click', 'handleClick') * .on('input', 'handleInput', '#form-container') * .emit('ready', { initialized: true }); * ``` */ on(type: string, handler: string | EventHandlerFunction, target?: string): this; /** * πŸ”— **Fluent API: Subscribe to Event** * * Chainable alias for on() - semantic sugar for event subscription patterns. * Identical functionality to on() but with clearer intent for event-driven architectures. * * @param type - Event type to subscribe to * @param handler - Handler method name or function * @param target - CSS selector for target element (defaults to 'document') * @returns this for method chaining * * @example * ```js * // Subscribe to custom events with clear intent * this.subscribe('data-updated', 'onDataUpdate') * .subscribe('user-login', 'onUserLogin') * .emit('app-ready', { loaded: true }); * ``` */ subscribe(type: string, handler: string | EventHandlerFunction, target?: string): this; /** * πŸ”— **Fluent API: Emit Custom Event** * * Chainable alias for dispatch() - emit custom events with fluent interface. * Supports string selectors and element targets for maximum flexibility. * * @param type - Event type to emit * @param detail - Event detail payload * @param target - CSS selector string or Element (defaults to document) * @returns this for method chaining * * @example * ```js * // Chain registrations and emissions in one statement * this.on('data-ready', 'handleData') * .emit('data-ready', { users: [...] }) * .emit('ui-update', { refresh: true }, '#main-content'); * ``` */ emit<T = any>(type: string, detail?: T, target?: string | EventTarget): this; } /** * 🎯 **Global Event Registry** - Ultimate Type Safety for Custom Events * * Declare your custom events in this global interface to get FULL type safety * and IntelliSense autocomplete for event names and payloads. * * @example * ```ts * // Extend the global event map * declare global { * interface YpsilonEventMap { * 'userLogin': { userId: string; timestamp: number }; * 'modalOpen': { modalId: string; context?: any }; * 'apiCall': { endpoint: string; method: 'GET' | 'POST' }; * } * } * ``` */ declare global { interface YpsilonEventMap { // Add your custom events here! // Example: // 'eventName': { userId: string; action: string }; } interface HTMLElement { /** * YpsilonEventHandler Instance (optional) * Stores a YpsilonEventHandler instance associated with this DOM element. */ ypsilonHandler?: YpsilonEventHandler; } interface Window { /** * Global YpsilonEventHandler Instance (optional) * Common pattern for SPA applications that use a single global handler. */ ypsilonHandler?: YpsilonEventHandler; } } // UMD-style exports for maximum compatibility declare const YpsilonEventHandler: typeof YpsilonEventHandler; export = YpsilonEventHandler; export as namespace YpsilonEventHandler; export default YpsilonEventHandler;