Source: event.mjs

/**
 * @module kongUtilEvent
 */
import utilEvent from "./core.mjs";

export * from "./core.mjs";

/**
 * @func listen
 * @desc Shortcut to `EventTarget.addEventListner`.
 * @param {EventTarget | string} target - target itself, or a CSS selector which matches an Element
 * @param  {...any} args - arguments for `addEventListener`
 * @returns {undefined}
 */
export function listen(target, ...args) {
    if (typeof target === 'string') target = document.querySelector(target);
    target.addEventListener(...args);
};

/**
 * @func unlisten
 * @desc Shortcut to `EventTarget.removeEventListener`
 * @param {EventTarget | string} target
 * @param  {...any} args - arguments for `removeEventListener`
 * @returns {undefined}
 */
export function unlisten(target, ...args) {
    if (typeof target === 'string') target = document.querySelector(target);
    target.removeEventListener(...args);
};


/**
 * @func listenMulti
 * @desc Add multi listener to multi events on multi targets
 * @param {string | Element | NodeList | Array.<EventTarget> } targets
 * @param {string | Array.<string>} eventTypes
 * @param {Function | Array.<Function>} listeners
 * @param {boolean | Object} [options]
 * @returns {undefined}
 */
export function listenMulti(targets, eventTypes, listeners, options) {
    if (typeof targets === 'string') targets = document.querySelectorAll(targets);
    if (! ('forEach' in targets)) targets = [targets];
    if (typeof eventTypes === 'string') eventTypes = eventTypes.split(',').map(s => s.trim());
    if (! ('forEach' in listeners)) listeners = [listeners];
    targets.forEach(target => {
        eventTypes.forEach(eventType => {
            listeners.forEach(listener => {
                target.addEventListener(eventType, listener, options);
            });
        });
    });
}

/**
 * @deprecated
 * @func listens
 */
export function listens() {
    console.warn('`listens()` is deprecated due to its name is too similar to `listen()`. Please change to `listenMulti()`, which has the same behavior.');
    listenMulti(...arguments);
}


/**
 * @func unlistenMulti
 * @desc Remove multi listener to multi events on multi targets
 * @param {string | NodeList | Array.<EventTarget> } targets
 * @param {string | Array.<string>} eventTypes
 * @param {Function | Array.<Function>} listener
 * @param {boolean | Object} [options]
 * @returns {undefined}
 */
export function unlistenMulti(targets, eventTypes, listeners, options) {
    if (typeof targets === 'string') targets = document.querySelectorAll(targets);
    if (typeof eventTypes === 'string') eventTypes = eventTypes.split(',').map(s => s.trim());
    if (typeof listeners === 'function') listeners = [listeners];
    targets.forEach(target => {
        eventTypes.forEach(eventType => {
            listeners.forEach(listener => {
                target.removeEventListener(eventType, listener, options);
            });
        });
    });
}

/**
 * @deprecated
 * @func listens
 */
export function unlistens() {
    console.warn('`unlistens()` is deprecated due to its name is too similar to `unlisten()`. Please change to `unlistenMulti()`, which has the same behavior.');
    unlistenMulti(...arguments);
}


/**
 * @func waitForEvent
 * @desc Resolves to the event, or rejects if `timeout` milliseconds passed.
 * @param {EventTarget | string} target
 * @param {string} type
 * @param {number | string | boolean | Object} [options]
 *  If omitted or being a non-positive number, the promise would never reject.
 *  If it's a positive number or a numeric string, the value is treated as the timeout.
 *  If it's a boolean, the value is used as `useCapture` for `EventTarget.addEventListener`.
 *  If it's an object, the property except the followings are used as `options` for `EventTarget.addEventListener`.
 * @param {number | string} [options.timeout] - timeout in milliseconds
 * @param {boolean} [options.preventDefault] - wheather to call `Event.preventDefault`
 * @param {boolean} [options.stopPropagation] - wheather to call `Event.stopPropagation`
 * @param {boolean} [options.stopImmediatePropagation] - wheather to call `Event.stopImmediatePropagation`
 *
 * @returns {Promise<Event>}
 *
 * @example /// rejects if no click to `document.body` in 1 second
    waitForEvent(document.body, "click", 1000)
    .then(() => console.log("a click event in 1 second is detected"));
 *
 */
export function waitForEvent(target, type, options) {
    let timeout;
    switch (typeof options) {
        case "undefined":   timeout = 0;                options = {};   break;
        case "number":      timeout = options;          options = {};   break;
        case "string":      timeout = parseInt(options);options = {};   break;
        case "boolean":     timeout = 0;                options = {capture: options}; break;
        case "object":      timeout = options.timeout;                  break;
        default: throw TypeError('unknown `option` format');
    }
    options.once = true;
    return new Promise((resolve, reject) => {
        const listener = event => {
            if (options.preventDefault) event.preventDefault();
            if (options.stopPropagation) event.stopPropagation();
            if (options.stopImmediatePropagation) event.stopImmediatePropagation();
            resolve();
        };
        listen(target, type, listener, options);
        if (timeout > 0) setTimeout(() => {
            unlisten(target, listener, options);
            const event = new Event("abort");
            target.dispatchEvent(event);
            reject(event);
        }, timeout);
    });
};

/**
 * @func extendEventTargetPrototype
 * @desc Add above methods to `EventTarget` class.
 */
export const extendEventTargetPrototype = () =>
    Object.assign(EventTarget.prototype, {
        listen: EventTarget.prototype.addEventListener,
        unlisten: EventTarget.prototype.removeEventListener,
        listenMulti: function() { listenMulti(this, ...arguments); },
        unlistenMulti: function() { unlistenMulti(this, ...arguments); },
        waitFor: function() { return waitForEvent(this, ...arguments); }
    })
;


Object.assign(utilEvent, {
    listen, unlisten,
    listens, unlistens,
    listenMulti, unlistenMulti,
    waitForEvent,
    extendEventTargetPrototype
});

export default utilEvent;