UNPKG

@avatijs/listener

Version:

Listener package part of Avati project

326 lines (316 loc) 11.6 kB
/*! * @avatijs/listener 0.1.1 * Copyright (c) 2024 Khaled Sameer <khaled.smq@hotmail.com> * Licensed under MIT, https://opensource.org/licenses/MIT/ * Please visit https://avati.io/ for details. */ /* eslint-disable */ import * as __WEBPACK_EXTERNAL_MODULE__avatijs_debounce_970167a9__ from "@avatijs/debounce"; import * as __WEBPACK_EXTERNAL_MODULE__avatijs_throttle_db391ff6__ from "@avatijs/throttle"; /******/ // The require scope /******/ var __webpack_require__ = {}; /******/ /************************************************************************/ /******/ /* webpack/runtime/define property getters */ /******/ (() => { /******/ // define getter functions for harmony exports /******/ __webpack_require__.d = (exports, definition) => { /******/ for(var key in definition) { /******/ if(__webpack_require__.o(definition, key) && !__webpack_require__.o(exports, key)) { /******/ Object.defineProperty(exports, key, { enumerable: true, get: definition[key] }); /******/ } /******/ } /******/ }; /******/ })(); /******/ /******/ /* webpack/runtime/hasOwnProperty shorthand */ /******/ (() => { /******/ __webpack_require__.o = (obj, prop) => (Object.prototype.hasOwnProperty.call(obj, prop)) /******/ })(); /******/ /************************************************************************/ var __webpack_exports__ = {}; // EXPORTS __webpack_require__.d(__webpack_exports__, { W: () => (/* reexport */ EventListenerManager) }); ;// external "@avatijs/debounce" var x = (y) => { var x = {}; __webpack_require__.d(x, y); return x } var y = (x) => (() => (x)) const debounce_namespaceObject = x({ ["debounce"]: () => (__WEBPACK_EXTERNAL_MODULE__avatijs_debounce_970167a9__.debounce) }); ;// external "@avatijs/throttle" var throttle_x = (y) => { var x = {}; __webpack_require__.d(x, y); return x } var throttle_y = (x) => (() => (x)) const throttle_namespaceObject = throttle_x({ ["throttle"]: () => (__WEBPACK_EXTERNAL_MODULE__avatijs_throttle_db391ff6__.throttle) }); ;// ./src/EventListenerManager.ts var _a, _b, _c; // Private symbol declarations const _listeners = Symbol('listeners'); const _weakRefMap = Symbol('weakRefMap'); const _handleWeakRef = Symbol('handleWeakRef'); const _validateParams = Symbol('validateParams'); const _generateEventId = Symbol('generateEventId'); const _eventIdCounter = Symbol('eventIdCounter'); class EventListenerManager { constructor() { Object.defineProperty(this, "defaultOptions", { enumerable: true, configurable: true, writable: true, value: void 0 }); Object.defineProperty(this, _a, { enumerable: true, configurable: true, writable: true, value: void 0 }); Object.defineProperty(this, _b, { enumerable: true, configurable: true, writable: true, value: void 0 }); Object.defineProperty(this, _c, { enumerable: true, configurable: true, writable: true, value: void 0 }); // TODO - Add more event mappings Object.defineProperty(this, "EVENT_MAPPINGS", { enumerable: true, configurable: true, writable: true, value: { debounce: new Set(['input', 'change', 'keyup', 'keydown', 'focus', 'blur', 'click']), throttle: new Set(['mousemove', 'scroll', 'resize', 'wheel']), } }); this[_listeners] = new Map(); this[_weakRefMap] = new WeakMap(); this[_eventIdCounter] = 0; // Bind methods to preserve context this.add = this.add.bind(this); this.remove = this.remove.bind(this); this.defaultOptions = Object.freeze({ capture: false, passive: true, once: false, async: false, }); } /** * Adds an event listener with advanced options. * @param element - The target element to attach the event listener. * @param eventType - The type of the event to listen for. * @param callback - The callback function to execute when the event is triggered. * @param options - Additional options for the event listener. * @returns A unique EventId representing the event listener. */ add(element, eventType, callback, options = {}) { this[_validateParams](element, eventType, callback); const eventId = this[_generateEventId](); const finalOptions = { ...this.defaultOptions, ...options }; let enhancedCallback = callback; if (finalOptions.debounce && finalOptions.throttle) { throw new Error('Cannot specify both debounce and throttle options'); } // Create the async wrapper first if needed if (finalOptions.async) { const asyncCallback = async (event) => { try { await callback(event); } catch (error) { if (finalOptions.onError && error instanceof Error) { finalOptions.onError(error); } else { throw error; } } }; enhancedCallback = asyncCallback; } // Apply debounce/throttle after async wrapper if (finalOptions.debounce) { // check recommendation this.recommendation(eventType, 'debounce'); enhancedCallback = (0,debounce_namespaceObject.debounce)(enhancedCallback, { wait: finalOptions.debounce, leading: finalOptions.leading, trailing: finalOptions.trailing, debug: finalOptions.debug, onError: finalOptions.onError, }); } else if (finalOptions.throttle) { this.recommendation(eventType, 'throttle'); enhancedCallback = (0,throttle_namespaceObject.throttle)(enhancedCallback, finalOptions.throttle, { leading: finalOptions.leading, trailing: finalOptions.trailing, onError: finalOptions.onError, }); } // Create wrapper that includes event metadata and error handling const wrappedCallback = async (event) => { if (finalOptions.metadata) { event.metadata = { timestamp: Date.now(), eventId, originalCallback: callback.name || 'anonymous', }; } try { await enhancedCallback.call(element, event); } catch (error) { if (finalOptions.onError && error instanceof Error) { finalOptions.onError(error); } else { throw error; } } // Handle 'once' option if (finalOptions.once) { this.remove(eventId); } }; // Store listener details const listenerDetails = { element: new WeakRef(element), eventType, callback: wrappedCallback, originalCallback: callback, options: finalOptions, timestamp: Date.now(), }; this[_listeners].set(eventId, listenerDetails); this[_handleWeakRef](element, eventId); element.addEventListener(eventType, wrappedCallback, finalOptions); return eventId; } /** * Removes an event listener */ remove(eventId) { const listener = this[_listeners].get(eventId); if (!listener) return false; const element = listener.element.deref(); if (element) { element.removeEventListener(listener.eventType, listener.callback, listener.options); // Remove eventId from _weakRefMap const elementRefs = this[_weakRefMap].get(element); if (elementRefs) { elementRefs.delete(eventId); if (elementRefs.size === 0) { this[_weakRefMap].delete(element); } } } this[_listeners].delete(eventId); return true; } /** * Adds event listener with automatic cleanup */ addWithCleanup(element, eventType, callback, options = {}) { const eventId = this.add(element, eventType, callback, options); return () => this.remove(eventId); } /** * Gets all active listeners for an element */ getListeners(element) { const elementRefs = this[_weakRefMap].get(element); if (!elementRefs) return []; return Array.from(elementRefs) .map((eventId) => { const listener = this[_listeners].get(eventId); if (!listener) return null; const el = listener.element.deref(); if (!el || el !== element) return null; return { eventId, eventType: listener.eventType, options: listener.options, timestamp: listener.timestamp, }; }) .filter((item) => item !== null); } /** * Removes all listeners from an element */ removeAll(element) { const elementRefs = this[_weakRefMap].get(element); if (!elementRefs) return; for (const eventId of elementRefs) { this.remove(eventId); } this[_weakRefMap].delete(element); } /** * Creates a one-time event listener that auto-removes */ once(element, eventType, callback, options = {}) { return this.add(element, eventType, callback, { ...options, once: true }); } /** * Validates input parameters * @private */ [(_a = _listeners, _b = _weakRefMap, _c = _eventIdCounter, _validateParams)](element, eventType, callback) { if (!(element instanceof Element || element instanceof Window || element instanceof Document)) { throw new TypeError('Element must be an instance of Element, Window, or Document'); } if (typeof eventType !== 'string') { throw new TypeError('Event type must be a string'); } if (typeof callback !== 'function') { throw new TypeError('Callback must be a function'); } } /** * Generates a unique event ID * @private */ [_generateEventId]() { return `event_${++this[_eventIdCounter]}`; } /** * Handles WeakRef management * @private */ [_handleWeakRef](element, eventId) { let elementRefs = this[_weakRefMap].get(element); if (!elementRefs) { elementRefs = new Set(); this[_weakRefMap].set(element, elementRefs); } elementRefs.add(eventId); } recommendation(eventType, limiterType) { const oppositeType = limiterType === 'throttle' ? 'debounce' : 'throttle'; if (this.EVENT_MAPPINGS[oppositeType].has(eventType)) { console.warn(`Event type '${eventType}' is recommended to be ${oppositeType}d instead of ${limiterType}d.`); } } } ;// ./src/index.ts var __webpack_exports__EventListenerManager = __webpack_exports__.W; export { __webpack_exports__EventListenerManager as EventListenerManager }; //# sourceMappingURL=index.js.map