@avatijs/listener
Version:
Listener package part of Avati project
970 lines (938 loc) • 33.2 kB
JavaScript
/*!
* @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 */
(function webpackUniversalModuleDefinition(root, factory) {
if(typeof exports === 'object' && typeof module === 'object')
module.exports = factory();
else if(typeof define === 'function' && define.amd)
define("Avati", [], factory);
else if(typeof exports === 'object')
exports["Avati"] = factory();
else
root["Avati"] = root["Avati"] || {}, root["Avati"]["Listener"] = factory();
})(typeof self !== 'undefined' ? self : this, () => {
return /******/ (() => { // webpackBootstrap
/******/ var __webpack_modules__ = ({
/***/ 747:
/***/ (function(module) {
/*!
* @avatijs/debounce 0.1.2
* 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 */
(function webpackUniversalModuleDefinition(root, factory) {
if(true)
module.exports = factory();
else {}
})(typeof self !== 'undefined' ? self : this, () => {
return /******/ (() => { // webpackBootstrap
/******/ "use strict";
/******/ // The require scope
/******/ var __nested_webpack_require_786__ = {};
/******/
/************************************************************************/
/******/ /* webpack/runtime/define property getters */
/******/ (() => {
/******/ // define getter functions for harmony exports
/******/ __nested_webpack_require_786__.d = (exports, definition) => {
/******/ for(var key in definition) {
/******/ if(__nested_webpack_require_786__.o(definition, key) && !__nested_webpack_require_786__.o(exports, key)) {
/******/ Object.defineProperty(exports, key, { enumerable: true, get: definition[key] });
/******/ }
/******/ }
/******/ };
/******/ })();
/******/
/******/ /* webpack/runtime/hasOwnProperty shorthand */
/******/ (() => {
/******/ __nested_webpack_require_786__.o = (obj, prop) => (Object.prototype.hasOwnProperty.call(obj, prop))
/******/ })();
/******/
/******/ /* webpack/runtime/make namespace object */
/******/ (() => {
/******/ // define __esModule on exports
/******/ __nested_webpack_require_786__.r = (exports) => {
/******/ if(typeof Symbol !== 'undefined' && Symbol.toStringTag) {
/******/ Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
/******/ }
/******/ Object.defineProperty(exports, '__esModule', { value: true });
/******/ };
/******/ })();
/******/
/************************************************************************/
var __nested_webpack_exports__ = {};
// ESM COMPAT FLAG
__nested_webpack_require_786__.r(__nested_webpack_exports__);
// EXPORTS
__nested_webpack_require_786__.d(__nested_webpack_exports__, {
debounce: () => (/* reexport */ debounce)
});
;// ./src/debouce.ts
/**
* Advanced debounce utility with TypeScript support
* Provides a way to limit the rate at which a function can fire by delaying its execution
* until after a specified amount of time has elapsed since its last invocation.
* @module debounce
*/
/** WeakMap to store private state for each debounced function */
const privateState = new WeakMap();
/**
* Creates a logger instance based on debug flag
* @param debug - Whether debug logging is enabled
* @returns Object with logging methods
* @private
*/
const createLogger = (debug) => ({
log: (...args) => debug && console.log('[Debounce]', ...args),
warn: (...args) => debug && console.warn('[Debounce]', ...args),
error: (...args) => debug && console.error('[Debounce]', ...args),
});
/**
* Creates a debounced version of the provided function that delays invoking func until after
* wait milliseconds have elapsed since the last time the debounced function was invoked.
*
* @template T - The type of the function to debounce
* @param {T} func - The function to debounce
* @param {DebounceOptions} [options={}] - Configuration options
* @returns {DebouncedFunction<T>} The debounced function
* @throws {TypeError} If func is not a function
* @throws {RangeError} If wait or maxWait values are invalid
* @throws {Error} If neither leading nor trailing is true
*
* @example
* const debouncedFn = debounce(async (x: number) => x * 2, { wait: 1000 });
* await debouncedFn(5); // Will execute after 1000ms of inactivity
* const debouncedFn = debounce(
* async (x: number) => x * 2,
* {
* wait: 1000,
* onError: (error) => console.error('Error in debounced function:', error)
* }
* );
*/
function debounce(func, options = {}) {
// Input validation
if (typeof func !== 'function') {
throw new TypeError('Expected a function');
}
const { wait = 0, leading = false, trailing = true, maxWait, debug = false, signal, onError, } = options;
// Validate options
if (wait < 0 || (maxWait !== undefined && maxWait < wait)) {
throw new RangeError('Invalid wait/maxWait values');
}
if (!leading && !trailing) {
throw new Error('At least one of leading or trailing must be true');
}
const logger = createLogger(debug);
const state = {
lastInvokeTime: 0,
pendingPromises: [],
aborted: false,
};
// Setup abort controller handling
if (signal) {
signal.addEventListener('abort', () => {
state.aborted = true;
cancel();
});
}
/**
* Cancels all active timers
* @private
*/
function cancelTimers() {
if (state.timerId !== undefined) {
clearTimeout(state.timerId);
state.timerId = undefined;
logger.log('Cleared debounce timer');
}
if (state.maxTimerId !== undefined) {
clearTimeout(state.maxTimerId);
state.maxTimerId = undefined;
logger.log('Cleared max wait timer');
}
}
/**
* Cancels any pending function invocations
* Rejects all pending promises and resets internal state
*/
function cancel() {
logger.log('Cancelling pending invocations');
cancelTimers();
state.lastInvokeTime = 0;
state.lastArgs = undefined;
state.lastThis = undefined;
state.lastCallTime = undefined;
const error = new Error('Debounced function cancelled');
handleError(error);
state.pendingPromises.forEach(({ reject }) => reject(new Error('Debounced function cancelled')));
state.pendingPromises = [];
}
/**
* Handles errors during function execution
* @param {Error} error - The error that occurred
* @private
*/
function handleError(error) {
logger.error('Error occurred:', error);
if (onError) {
try {
onError(error);
}
catch (callbackError) {
logger.error('Error in onError callback:', callbackError);
}
}
}
/**
* Checks if there are any pending function invocations
* @returns {boolean} True if there are pending invocations
*/
function pending() {
return state.timerId !== undefined;
}
/**
* Determines if the function should be invoked based on timing conditions
* @param {number} time - Current timestamp
* @returns {boolean} True if function should be invoked
* @private
*/
function shouldInvoke(time) {
if (state.aborted)
return false;
const timeSinceLastCall = state.lastCallTime === undefined ? 0 : time - state.lastCallTime;
const timeSinceLastInvoke = time - state.lastInvokeTime;
return (state.lastCallTime === undefined ||
timeSinceLastCall >= wait ||
timeSinceLastCall < 0 ||
(maxWait !== undefined && timeSinceLastInvoke >= maxWait));
}
/**
* Executes the underlying function and manages promise resolution
* @param {number} time - Current timestamp
* @returns {Promise} Promise resolving to function result
* @private
*/
async function invokeFunc(time) {
logger.log(`Invoking function at ${time}`);
state.lastInvokeTime = time;
const args = state.lastArgs;
const thisArg = state.lastThis;
state.lastArgs = undefined;
state.lastThis = undefined;
try {
const result = await func.apply(thisArg, args);
state.result = result;
state.pendingPromises.forEach(({ resolve }) => resolve(result));
state.pendingPromises = [];
logger.log('Function invoked successfully', result);
return result;
}
catch (error) {
const wrappedError = error instanceof Error ? error : new Error(String(error));
logger.error('Error in function invocation:', wrappedError);
handleError(wrappedError);
// Clear pending promises after handling error
const currentPromises = [...state.pendingPromises];
state.pendingPromises = [];
// Reject all pending promises
currentPromises.forEach(({ reject }) => reject(wrappedError));
}
}
/**
* Starts both the debounce timer and maxWait timer if configured
* @param {number} time - Current timestamp
* @private
*/
function startTimer(time) {
const remainingTime = remainingWait(time);
state.timerId = setTimeout(timerExpired, remainingTime);
logger.log(`Started debounce timer for ${remainingTime}ms`);
if (maxWait !== undefined && !state.maxTimerId) {
const timeToMaxWait = maxWait - (time - state.lastCallTime);
state.maxTimerId = setTimeout(() => {
logger.log('Max wait timer expired');
cancelTimers();
invokeFunc(Date.now());
}, Math.max(0, timeToMaxWait));
logger.log(`Started max wait timer for ${timeToMaxWait}ms`);
}
}
/**
* Calculates remaining wait time before next execution
* @param {number} time - Current timestamp
* @returns {number} Milliseconds until next allowed execution
* @private
*/
function remainingWait(time) {
const timeSinceLastCall = state.lastCallTime ? time - state.lastCallTime : 0;
return Math.max(0, wait - timeSinceLastCall);
}
/**
* Handles timer expiration
* @private
*/
function timerExpired() {
const time = Date.now();
logger.log('Debounce timer expired');
if (shouldInvoke(time)) {
return trailingEdge(time);
}
startTimer(time);
}
/**
* Handles leading edge execution
* @param {number} time - Current timestamp
* @private
*/
function leadingEdge(time) {
logger.log('Leading edge triggered');
state.lastInvokeTime = time;
startTimer(time);
if (leading) {
invokeFunc(time);
}
}
/**
* Handles trailing edge execution
* @param {number} time - Current timestamp
* @private
*/
function trailingEdge(time) {
logger.log('Trailing edge triggered');
cancelTimers();
if (trailing && state.lastArgs) {
invokeFunc(time);
}
else {
state.pendingPromises.forEach(({ resolve }) => {
resolve(state.result);
});
state.pendingPromises = [];
}
}
/**
* Immediately executes the debounced function
* @param {...Parameters<T>} args - Function arguments
* @returns {Promise<ReturnType<T>>} Promise resolving to function result
*/
async function flush(...args) {
logger.log('Flush requested');
const argsToUse = args.length > 0 ? args : state.lastArgs;
const thisArg = state.lastThis;
cancelTimers();
if (argsToUse) {
state.lastArgs = argsToUse;
state.lastThis = thisArg;
return invokeFunc(Date.now());
}
return Promise.resolve(state.result);
}
/**
* Cleans up resources used by the debounced function
*/
function cleanup() {
logger.log('Cleanup initiated');
cancel();
privateState.delete(debounced);
}
/**
* The debounced function that wraps the original
* @param {...Parameters<T>} args - Function arguments
* @returns {Promise<ReturnType<T>>} Promise resolving to function result
*/
const debounced = function (...args) {
if (state.aborted) {
return Promise.reject(new Error('Debounced function aborted'));
}
const time = Date.now();
const isInvoking = shouldInvoke(time);
state.lastArgs = args;
state.lastThis = this;
state.lastCallTime = time;
logger.log('Function called', {
time,
isInvoking,
args,
pending: pending(),
});
return new Promise((resolve, reject) => {
state.pendingPromises.push({ resolve, reject });
if (state.timerId === undefined) {
leadingEdge(time);
}
else {
cancelTimers();
startTimer(time);
}
});
};
// Store private state
privateState.set(debounced, state);
// Add utility methods
Object.defineProperties(debounced, {
cancel: { value: cancel, writable: false, configurable: false },
flush: { value: flush, writable: false, configurable: false },
pending: { value: pending, writable: false, configurable: false },
cleanup: { value: cleanup, writable: false, configurable: false },
});
return debounced;
}
;// ./src/index.ts
/******/ return __nested_webpack_exports__;
/******/ })()
;
});
//# sourceMappingURL=index.js.map
/***/ }),
/***/ 94:
/***/ (function(module) {
/*!
* @avatijs/throttle 0.1.2
* 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 */
(function webpackUniversalModuleDefinition(root, factory) {
if(true)
module.exports = factory();
else {}
})(typeof self !== 'undefined' ? self : this, () => {
return /******/ (() => { // webpackBootstrap
/******/ "use strict";
/******/ // The require scope
/******/ var __nested_webpack_require_786__ = {};
/******/
/************************************************************************/
/******/ /* webpack/runtime/define property getters */
/******/ (() => {
/******/ // define getter functions for harmony exports
/******/ __nested_webpack_require_786__.d = (exports, definition) => {
/******/ for(var key in definition) {
/******/ if(__nested_webpack_require_786__.o(definition, key) && !__nested_webpack_require_786__.o(exports, key)) {
/******/ Object.defineProperty(exports, key, { enumerable: true, get: definition[key] });
/******/ }
/******/ }
/******/ };
/******/ })();
/******/
/******/ /* webpack/runtime/hasOwnProperty shorthand */
/******/ (() => {
/******/ __nested_webpack_require_786__.o = (obj, prop) => (Object.prototype.hasOwnProperty.call(obj, prop))
/******/ })();
/******/
/******/ /* webpack/runtime/make namespace object */
/******/ (() => {
/******/ // define __esModule on exports
/******/ __nested_webpack_require_786__.r = (exports) => {
/******/ if(typeof Symbol !== 'undefined' && Symbol.toStringTag) {
/******/ Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
/******/ }
/******/ Object.defineProperty(exports, '__esModule', { value: true });
/******/ };
/******/ })();
/******/
/************************************************************************/
var __nested_webpack_exports__ = {};
// ESM COMPAT FLAG
__nested_webpack_require_786__.r(__nested_webpack_exports__);
// EXPORTS
__nested_webpack_require_786__.d(__nested_webpack_exports__, {
throttle: () => (/* reexport */ throttle)
});
;// ./src/throttle.ts
/**
* Creates a throttled version function that limits the rate at which it executes.
*
* @param callback - The function to throttle
* @param limit - The minimum time (in milliseconds) that must pass between function executions defaults to 120ms
* @param {ThrottleOptions} options - Configuration options for the throttle behavior
*
* @returns A throttled version of the provided callback function
*
* @example
* const throttledHandler = throttle(
* (event: MouseEvent) => console.log(event.clientX),
* 250,
* { leading: false, trailing: false }
* );
* window.addEventListener('mousemove', throttledHandler);
*/
function throttle(callback, limit = 1000 / 120, // 120 FPS
options = {}) {
let timeoutId;
let lastCallTime = null;
let savedArgs = null;
let savedThis = null;
let isExecuting = false;
const { leading = true, trailing = true, onError } = options;
/**
* Invokes the callback with the saved context and arguments.
*/
const invokeCallback = () => {
if (savedArgs === null)
return;
isExecuting = true;
const startTime = now();
try {
callback.apply(savedThis, savedArgs);
}
catch (error) {
if (onError) {
onError(error instanceof Error ? error : new Error(String(error)));
}
else {
throw error;
}
}
finally {
isExecuting = false;
}
lastCallTime = now();
savedArgs = null;
savedThis = null;
// Track execution time
const executionTime = now() - startTime;
if (executionTime > limit) {
console.warn(`Execution time (${executionTime}ms) exceeded throttle limit (${limit}ms)`);
}
};
/**
* Handles trailing edge execution with optimized timing
*/
const trailingExecute = () => {
if (timeoutId) {
clearTimeout(timeoutId);
timeoutId = undefined;
if (!isExecuting && trailing && savedArgs) {
const timeSinceLastCall = lastCallTime ? now() - lastCallTime : Infinity;
// Only execute if enough time has passed
if (timeSinceLastCall >= limit) {
invokeCallback();
return true;
}
}
}
return false;
};
/**
* Starts the timer for the trailing edge invocation.
* @param remaining - The remaining time before the callback should be invoked.
*/
const startTimer = (remaining) => {
timeoutId = setTimeout(() => {
timeoutId = undefined;
if (trailing) {
invokeCallback();
}
}, remaining);
};
const now = typeof performance !== 'undefined' ? () => performance.now() : () => Date.now();
/**
* The throttled function that controls the execution rate of the callback.
*/
const throttled = function (...args) {
const _now = now();
// Handle first call optimization
if (lastCallTime === null) {
if (leading) {
savedArgs = args;
// eslint-disable-next-line @typescript-eslint/no-this-alias
savedThis = this;
invokeCallback();
return;
}
lastCallTime = _now;
}
const remaining = lastCallTime ? limit - (_now - lastCallTime) : 0;
savedArgs = args;
// eslint-disable-next-line @typescript-eslint/no-this-alias
savedThis = this;
if (remaining < 0 || remaining > limit) {
if (timeoutId) {
clearTimeout(timeoutId);
timeoutId = undefined;
}
lastCallTime = _now;
invokeCallback();
}
else if (!timeoutId && trailing) {
startTimer(remaining);
}
};
/**
* Cancels any pending executions and resets the throttle state.
*/
throttled.cancel = () => {
if (timeoutId) {
clearTimeout(timeoutId);
}
lastCallTime = null;
savedArgs = null;
savedThis = null;
timeoutId = undefined;
isExecuting = false;
};
/**
* Immediately invokes the pending execution of the throttled function.
*/
throttled.flush = () => {
trailingExecute();
};
return throttled;
}
;// ./src/index.ts
/******/ return __nested_webpack_exports__;
/******/ })()
;
});
//# sourceMappingURL=index.js.map
/***/ })
/******/ });
/************************************************************************/
/******/ // The module cache
/******/ var __webpack_module_cache__ = {};
/******/
/******/ // The require function
/******/ function __webpack_require__(moduleId) {
/******/ // Check if module is in cache
/******/ var cachedModule = __webpack_module_cache__[moduleId];
/******/ if (cachedModule !== undefined) {
/******/ return cachedModule.exports;
/******/ }
/******/ // Create a new module (and put it into the cache)
/******/ var module = __webpack_module_cache__[moduleId] = {
/******/ // no module.id needed
/******/ // no module.loaded needed
/******/ exports: {}
/******/ };
/******/
/******/ // Execute the module function
/******/ __webpack_modules__[moduleId].call(module.exports, module, module.exports, __webpack_require__);
/******/
/******/ // Return the exports of the module
/******/ return module.exports;
/******/ }
/******/
/************************************************************************/
/******/ /* 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))
/******/ })();
/******/
/******/ /* webpack/runtime/make namespace object */
/******/ (() => {
/******/ // define __esModule on exports
/******/ __webpack_require__.r = (exports) => {
/******/ if(typeof Symbol !== 'undefined' && Symbol.toStringTag) {
/******/ Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
/******/ }
/******/ Object.defineProperty(exports, '__esModule', { value: true });
/******/ };
/******/ })();
/******/
/************************************************************************/
var __webpack_exports__ = {};
// This entry needs to be wrapped in an IIFE because it needs to be in strict mode.
(() => {
"use strict";
// ESM COMPAT FLAG
__webpack_require__.r(__webpack_exports__);
// EXPORTS
__webpack_require__.d(__webpack_exports__, {
EventListenerManager: () => (/* reexport */ EventListenerManager)
});
// EXTERNAL MODULE: ../debounce/dist/umd/index.js
var umd = __webpack_require__(747);
// EXTERNAL MODULE: ../throttle/dist/umd/index.js
var dist_umd = __webpack_require__(94);
;// ./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,umd.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,dist_umd.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
})();
/******/ return __webpack_exports__;
/******/ })()
;
});
//# sourceMappingURL=index.js.map