You can not select more than 25 topics
			Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
		
		
		
		
		
			
		
			
				
					863 lines
				
				23 KiB
			
		
		
			
		
	
	
					863 lines
				
				23 KiB
			| 
											3 years ago
										 | /** | ||
|  |  * @author Toru Nagashima <https://github.com/mysticatea>
 | ||
|  |  * @copyright 2015 Toru Nagashima. All rights reserved. | ||
|  |  * See LICENSE file in root directory for full license. | ||
|  |  */ | ||
|  | /** | ||
|  |  * @typedef {object} PrivateData | ||
|  |  * @property {EventTarget} eventTarget The event target. | ||
|  |  * @property {{type:string}} event The original event object. | ||
|  |  * @property {number} eventPhase The current event phase. | ||
|  |  * @property {EventTarget|null} currentTarget The current event target. | ||
|  |  * @property {boolean} canceled The flag to prevent default. | ||
|  |  * @property {boolean} stopped The flag to stop propagation. | ||
|  |  * @property {boolean} immediateStopped The flag to stop propagation immediately. | ||
|  |  * @property {Function|null} passiveListener The listener if the current listener is passive. Otherwise this is null. | ||
|  |  * @property {number} timeStamp The unix time. | ||
|  |  * @private | ||
|  |  */ | ||
|  | 
 | ||
|  | /** | ||
|  |  * Private data for event wrappers. | ||
|  |  * @type {WeakMap<Event, PrivateData>} | ||
|  |  * @private | ||
|  |  */ | ||
|  | const privateData = new WeakMap(); | ||
|  | 
 | ||
|  | /** | ||
|  |  * Cache for wrapper classes. | ||
|  |  * @type {WeakMap<Object, Function>} | ||
|  |  * @private | ||
|  |  */ | ||
|  | const wrappers = new WeakMap(); | ||
|  | 
 | ||
|  | /** | ||
|  |  * Get private data. | ||
|  |  * @param {Event} event The event object to get private data. | ||
|  |  * @returns {PrivateData} The private data of the event. | ||
|  |  * @private | ||
|  |  */ | ||
|  | function pd(event) { | ||
|  |     const retv = privateData.get(event); | ||
|  |     console.assert( | ||
|  |         retv != null, | ||
|  |         "'this' is expected an Event object, but got", | ||
|  |         event | ||
|  |     ); | ||
|  |     return retv | ||
|  | } | ||
|  | 
 | ||
|  | /** | ||
|  |  * https://dom.spec.whatwg.org/#set-the-canceled-flag
 | ||
|  |  * @param data {PrivateData} private data. | ||
|  |  */ | ||
|  | function setCancelFlag(data) { | ||
|  |     if (data.passiveListener != null) { | ||
|  |         if ( | ||
|  |             typeof console !== "undefined" && | ||
|  |             typeof console.error === "function" | ||
|  |         ) { | ||
|  |             console.error( | ||
|  |                 "Unable to preventDefault inside passive event listener invocation.", | ||
|  |                 data.passiveListener | ||
|  |             ); | ||
|  |         } | ||
|  |         return | ||
|  |     } | ||
|  |     if (!data.event.cancelable) { | ||
|  |         return | ||
|  |     } | ||
|  | 
 | ||
|  |     data.canceled = true; | ||
|  |     if (typeof data.event.preventDefault === "function") { | ||
|  |         data.event.preventDefault(); | ||
|  |     } | ||
|  | } | ||
|  | 
 | ||
|  | /** | ||
|  |  * @see https://dom.spec.whatwg.org/#interface-event
 | ||
|  |  * @private | ||
|  |  */ | ||
|  | /** | ||
|  |  * The event wrapper. | ||
|  |  * @constructor | ||
|  |  * @param {EventTarget} eventTarget The event target of this dispatching. | ||
|  |  * @param {Event|{type:string}} event The original event to wrap. | ||
|  |  */ | ||
|  | function Event(eventTarget, event) { | ||
|  |     privateData.set(this, { | ||
|  |         eventTarget, | ||
|  |         event, | ||
|  |         eventPhase: 2, | ||
|  |         currentTarget: eventTarget, | ||
|  |         canceled: false, | ||
|  |         stopped: false, | ||
|  |         immediateStopped: false, | ||
|  |         passiveListener: null, | ||
|  |         timeStamp: event.timeStamp || Date.now(), | ||
|  |     }); | ||
|  | 
 | ||
|  |     // https://heycam.github.io/webidl/#Unforgeable
 | ||
|  |     Object.defineProperty(this, "isTrusted", { value: false, enumerable: true }); | ||
|  | 
 | ||
|  |     // Define accessors
 | ||
|  |     const keys = Object.keys(event); | ||
|  |     for (let i = 0; i < keys.length; ++i) { | ||
|  |         const key = keys[i]; | ||
|  |         if (!(key in this)) { | ||
|  |             Object.defineProperty(this, key, defineRedirectDescriptor(key)); | ||
|  |         } | ||
|  |     } | ||
|  | } | ||
|  | 
 | ||
|  | // Should be enumerable, but class methods are not enumerable.
 | ||
|  | Event.prototype = { | ||
|  |     /** | ||
|  |      * The type of this event. | ||
|  |      * @type {string} | ||
|  |      */ | ||
|  |     get type() { | ||
|  |         return pd(this).event.type | ||
|  |     }, | ||
|  | 
 | ||
|  |     /** | ||
|  |      * The target of this event. | ||
|  |      * @type {EventTarget} | ||
|  |      */ | ||
|  |     get target() { | ||
|  |         return pd(this).eventTarget | ||
|  |     }, | ||
|  | 
 | ||
|  |     /** | ||
|  |      * The target of this event. | ||
|  |      * @type {EventTarget} | ||
|  |      */ | ||
|  |     get currentTarget() { | ||
|  |         return pd(this).currentTarget | ||
|  |     }, | ||
|  | 
 | ||
|  |     /** | ||
|  |      * @returns {EventTarget[]} The composed path of this event. | ||
|  |      */ | ||
|  |     composedPath() { | ||
|  |         const currentTarget = pd(this).currentTarget; | ||
|  |         if (currentTarget == null) { | ||
|  |             return [] | ||
|  |         } | ||
|  |         return [currentTarget] | ||
|  |     }, | ||
|  | 
 | ||
|  |     /** | ||
|  |      * Constant of NONE. | ||
|  |      * @type {number} | ||
|  |      */ | ||
|  |     get NONE() { | ||
|  |         return 0 | ||
|  |     }, | ||
|  | 
 | ||
|  |     /** | ||
|  |      * Constant of CAPTURING_PHASE. | ||
|  |      * @type {number} | ||
|  |      */ | ||
|  |     get CAPTURING_PHASE() { | ||
|  |         return 1 | ||
|  |     }, | ||
|  | 
 | ||
|  |     /** | ||
|  |      * Constant of AT_TARGET. | ||
|  |      * @type {number} | ||
|  |      */ | ||
|  |     get AT_TARGET() { | ||
|  |         return 2 | ||
|  |     }, | ||
|  | 
 | ||
|  |     /** | ||
|  |      * Constant of BUBBLING_PHASE. | ||
|  |      * @type {number} | ||
|  |      */ | ||
|  |     get BUBBLING_PHASE() { | ||
|  |         return 3 | ||
|  |     }, | ||
|  | 
 | ||
|  |     /** | ||
|  |      * The target of this event. | ||
|  |      * @type {number} | ||
|  |      */ | ||
|  |     get eventPhase() { | ||
|  |         return pd(this).eventPhase | ||
|  |     }, | ||
|  | 
 | ||
|  |     /** | ||
|  |      * Stop event bubbling. | ||
|  |      * @returns {void} | ||
|  |      */ | ||
|  |     stopPropagation() { | ||
|  |         const data = pd(this); | ||
|  | 
 | ||
|  |         data.stopped = true; | ||
|  |         if (typeof data.event.stopPropagation === "function") { | ||
|  |             data.event.stopPropagation(); | ||
|  |         } | ||
|  |     }, | ||
|  | 
 | ||
|  |     /** | ||
|  |      * Stop event bubbling. | ||
|  |      * @returns {void} | ||
|  |      */ | ||
|  |     stopImmediatePropagation() { | ||
|  |         const data = pd(this); | ||
|  | 
 | ||
|  |         data.stopped = true; | ||
|  |         data.immediateStopped = true; | ||
|  |         if (typeof data.event.stopImmediatePropagation === "function") { | ||
|  |             data.event.stopImmediatePropagation(); | ||
|  |         } | ||
|  |     }, | ||
|  | 
 | ||
|  |     /** | ||
|  |      * The flag to be bubbling. | ||
|  |      * @type {boolean} | ||
|  |      */ | ||
|  |     get bubbles() { | ||
|  |         return Boolean(pd(this).event.bubbles) | ||
|  |     }, | ||
|  | 
 | ||
|  |     /** | ||
|  |      * The flag to be cancelable. | ||
|  |      * @type {boolean} | ||
|  |      */ | ||
|  |     get cancelable() { | ||
|  |         return Boolean(pd(this).event.cancelable) | ||
|  |     }, | ||
|  | 
 | ||
|  |     /** | ||
|  |      * Cancel this event. | ||
|  |      * @returns {void} | ||
|  |      */ | ||
|  |     preventDefault() { | ||
|  |         setCancelFlag(pd(this)); | ||
|  |     }, | ||
|  | 
 | ||
|  |     /** | ||
|  |      * The flag to indicate cancellation state. | ||
|  |      * @type {boolean} | ||
|  |      */ | ||
|  |     get defaultPrevented() { | ||
|  |         return pd(this).canceled | ||
|  |     }, | ||
|  | 
 | ||
|  |     /** | ||
|  |      * The flag to be composed. | ||
|  |      * @type {boolean} | ||
|  |      */ | ||
|  |     get composed() { | ||
|  |         return Boolean(pd(this).event.composed) | ||
|  |     }, | ||
|  | 
 | ||
|  |     /** | ||
|  |      * The unix time of this event. | ||
|  |      * @type {number} | ||
|  |      */ | ||
|  |     get timeStamp() { | ||
|  |         return pd(this).timeStamp | ||
|  |     }, | ||
|  | 
 | ||
|  |     /** | ||
|  |      * The target of this event. | ||
|  |      * @type {EventTarget} | ||
|  |      * @deprecated | ||
|  |      */ | ||
|  |     get srcElement() { | ||
|  |         return pd(this).eventTarget | ||
|  |     }, | ||
|  | 
 | ||
|  |     /** | ||
|  |      * The flag to stop event bubbling. | ||
|  |      * @type {boolean} | ||
|  |      * @deprecated | ||
|  |      */ | ||
|  |     get cancelBubble() { | ||
|  |         return pd(this).stopped | ||
|  |     }, | ||
|  |     set cancelBubble(value) { | ||
|  |         if (!value) { | ||
|  |             return | ||
|  |         } | ||
|  |         const data = pd(this); | ||
|  | 
 | ||
|  |         data.stopped = true; | ||
|  |         if (typeof data.event.cancelBubble === "boolean") { | ||
|  |             data.event.cancelBubble = true; | ||
|  |         } | ||
|  |     }, | ||
|  | 
 | ||
|  |     /** | ||
|  |      * The flag to indicate cancellation state. | ||
|  |      * @type {boolean} | ||
|  |      * @deprecated | ||
|  |      */ | ||
|  |     get returnValue() { | ||
|  |         return !pd(this).canceled | ||
|  |     }, | ||
|  |     set returnValue(value) { | ||
|  |         if (!value) { | ||
|  |             setCancelFlag(pd(this)); | ||
|  |         } | ||
|  |     }, | ||
|  | 
 | ||
|  |     /** | ||
|  |      * Initialize this event object. But do nothing under event dispatching. | ||
|  |      * @param {string} type The event type. | ||
|  |      * @param {boolean} [bubbles=false] The flag to be possible to bubble up. | ||
|  |      * @param {boolean} [cancelable=false] The flag to be possible to cancel. | ||
|  |      * @deprecated | ||
|  |      */ | ||
|  |     initEvent() { | ||
|  |         // Do nothing.
 | ||
|  |     }, | ||
|  | }; | ||
|  | 
 | ||
|  | // `constructor` is not enumerable.
 | ||
|  | Object.defineProperty(Event.prototype, "constructor", { | ||
|  |     value: Event, | ||
|  |     configurable: true, | ||
|  |     writable: true, | ||
|  | }); | ||
|  | 
 | ||
|  | // Ensure `event instanceof window.Event` is `true`.
 | ||
|  | if (typeof window !== "undefined" && typeof window.Event !== "undefined") { | ||
|  |     Object.setPrototypeOf(Event.prototype, window.Event.prototype); | ||
|  | 
 | ||
|  |     // Make association for wrappers.
 | ||
|  |     wrappers.set(window.Event.prototype, Event); | ||
|  | } | ||
|  | 
 | ||
|  | /** | ||
|  |  * Get the property descriptor to redirect a given property. | ||
|  |  * @param {string} key Property name to define property descriptor. | ||
|  |  * @returns {PropertyDescriptor} The property descriptor to redirect the property. | ||
|  |  * @private | ||
|  |  */ | ||
|  | function defineRedirectDescriptor(key) { | ||
|  |     return { | ||
|  |         get() { | ||
|  |             return pd(this).event[key] | ||
|  |         }, | ||
|  |         set(value) { | ||
|  |             pd(this).event[key] = value; | ||
|  |         }, | ||
|  |         configurable: true, | ||
|  |         enumerable: true, | ||
|  |     } | ||
|  | } | ||
|  | 
 | ||
|  | /** | ||
|  |  * Get the property descriptor to call a given method property. | ||
|  |  * @param {string} key Property name to define property descriptor. | ||
|  |  * @returns {PropertyDescriptor} The property descriptor to call the method property. | ||
|  |  * @private | ||
|  |  */ | ||
|  | function defineCallDescriptor(key) { | ||
|  |     return { | ||
|  |         value() { | ||
|  |             const event = pd(this).event; | ||
|  |             return event[key].apply(event, arguments) | ||
|  |         }, | ||
|  |         configurable: true, | ||
|  |         enumerable: true, | ||
|  |     } | ||
|  | } | ||
|  | 
 | ||
|  | /** | ||
|  |  * Define new wrapper class. | ||
|  |  * @param {Function} BaseEvent The base wrapper class. | ||
|  |  * @param {Object} proto The prototype of the original event. | ||
|  |  * @returns {Function} The defined wrapper class. | ||
|  |  * @private | ||
|  |  */ | ||
|  | function defineWrapper(BaseEvent, proto) { | ||
|  |     const keys = Object.keys(proto); | ||
|  |     if (keys.length === 0) { | ||
|  |         return BaseEvent | ||
|  |     } | ||
|  | 
 | ||
|  |     /** CustomEvent */ | ||
|  |     function CustomEvent(eventTarget, event) { | ||
|  |         BaseEvent.call(this, eventTarget, event); | ||
|  |     } | ||
|  | 
 | ||
|  |     CustomEvent.prototype = Object.create(BaseEvent.prototype, { | ||
|  |         constructor: { value: CustomEvent, configurable: true, writable: true }, | ||
|  |     }); | ||
|  | 
 | ||
|  |     // Define accessors.
 | ||
|  |     for (let i = 0; i < keys.length; ++i) { | ||
|  |         const key = keys[i]; | ||
|  |         if (!(key in BaseEvent.prototype)) { | ||
|  |             const descriptor = Object.getOwnPropertyDescriptor(proto, key); | ||
|  |             const isFunc = typeof descriptor.value === "function"; | ||
|  |             Object.defineProperty( | ||
|  |                 CustomEvent.prototype, | ||
|  |                 key, | ||
|  |                 isFunc | ||
|  |                     ? defineCallDescriptor(key) | ||
|  |                     : defineRedirectDescriptor(key) | ||
|  |             ); | ||
|  |         } | ||
|  |     } | ||
|  | 
 | ||
|  |     return CustomEvent | ||
|  | } | ||
|  | 
 | ||
|  | /** | ||
|  |  * Get the wrapper class of a given prototype. | ||
|  |  * @param {Object} proto The prototype of the original event to get its wrapper. | ||
|  |  * @returns {Function} The wrapper class. | ||
|  |  * @private | ||
|  |  */ | ||
|  | function getWrapper(proto) { | ||
|  |     if (proto == null || proto === Object.prototype) { | ||
|  |         return Event | ||
|  |     } | ||
|  | 
 | ||
|  |     let wrapper = wrappers.get(proto); | ||
|  |     if (wrapper == null) { | ||
|  |         wrapper = defineWrapper(getWrapper(Object.getPrototypeOf(proto)), proto); | ||
|  |         wrappers.set(proto, wrapper); | ||
|  |     } | ||
|  |     return wrapper | ||
|  | } | ||
|  | 
 | ||
|  | /** | ||
|  |  * Wrap a given event to management a dispatching. | ||
|  |  * @param {EventTarget} eventTarget The event target of this dispatching. | ||
|  |  * @param {Object} event The event to wrap. | ||
|  |  * @returns {Event} The wrapper instance. | ||
|  |  * @private | ||
|  |  */ | ||
|  | function wrapEvent(eventTarget, event) { | ||
|  |     const Wrapper = getWrapper(Object.getPrototypeOf(event)); | ||
|  |     return new Wrapper(eventTarget, event) | ||
|  | } | ||
|  | 
 | ||
|  | /** | ||
|  |  * Get the immediateStopped flag of a given event. | ||
|  |  * @param {Event} event The event to get. | ||
|  |  * @returns {boolean} The flag to stop propagation immediately. | ||
|  |  * @private | ||
|  |  */ | ||
|  | function isStopped(event) { | ||
|  |     return pd(event).immediateStopped | ||
|  | } | ||
|  | 
 | ||
|  | /** | ||
|  |  * Set the current event phase of a given event. | ||
|  |  * @param {Event} event The event to set current target. | ||
|  |  * @param {number} eventPhase New event phase. | ||
|  |  * @returns {void} | ||
|  |  * @private | ||
|  |  */ | ||
|  | function setEventPhase(event, eventPhase) { | ||
|  |     pd(event).eventPhase = eventPhase; | ||
|  | } | ||
|  | 
 | ||
|  | /** | ||
|  |  * Set the current target of a given event. | ||
|  |  * @param {Event} event The event to set current target. | ||
|  |  * @param {EventTarget|null} currentTarget New current target. | ||
|  |  * @returns {void} | ||
|  |  * @private | ||
|  |  */ | ||
|  | function setCurrentTarget(event, currentTarget) { | ||
|  |     pd(event).currentTarget = currentTarget; | ||
|  | } | ||
|  | 
 | ||
|  | /** | ||
|  |  * Set a passive listener of a given event. | ||
|  |  * @param {Event} event The event to set current target. | ||
|  |  * @param {Function|null} passiveListener New passive listener. | ||
|  |  * @returns {void} | ||
|  |  * @private | ||
|  |  */ | ||
|  | function setPassiveListener(event, passiveListener) { | ||
|  |     pd(event).passiveListener = passiveListener; | ||
|  | } | ||
|  | 
 | ||
|  | /** | ||
|  |  * @typedef {object} ListenerNode | ||
|  |  * @property {Function} listener | ||
|  |  * @property {1|2|3} listenerType | ||
|  |  * @property {boolean} passive | ||
|  |  * @property {boolean} once | ||
|  |  * @property {ListenerNode|null} next | ||
|  |  * @private | ||
|  |  */ | ||
|  | 
 | ||
|  | /** | ||
|  |  * @type {WeakMap<object, Map<string, ListenerNode>>} | ||
|  |  * @private | ||
|  |  */ | ||
|  | const listenersMap = new WeakMap(); | ||
|  | 
 | ||
|  | // Listener types
 | ||
|  | const CAPTURE = 1; | ||
|  | const BUBBLE = 2; | ||
|  | const ATTRIBUTE = 3; | ||
|  | 
 | ||
|  | /** | ||
|  |  * Check whether a given value is an object or not. | ||
|  |  * @param {any} x The value to check. | ||
|  |  * @returns {boolean} `true` if the value is an object. | ||
|  |  */ | ||
|  | function isObject(x) { | ||
|  |     return x !== null && typeof x === "object" //eslint-disable-line no-restricted-syntax
 | ||
|  | } | ||
|  | 
 | ||
|  | /** | ||
|  |  * Get listeners. | ||
|  |  * @param {EventTarget} eventTarget The event target to get. | ||
|  |  * @returns {Map<string, ListenerNode>} The listeners. | ||
|  |  * @private | ||
|  |  */ | ||
|  | function getListeners(eventTarget) { | ||
|  |     const listeners = listenersMap.get(eventTarget); | ||
|  |     if (listeners == null) { | ||
|  |         throw new TypeError( | ||
|  |             "'this' is expected an EventTarget object, but got another value." | ||
|  |         ) | ||
|  |     } | ||
|  |     return listeners | ||
|  | } | ||
|  | 
 | ||
|  | /** | ||
|  |  * Get the property descriptor for the event attribute of a given event. | ||
|  |  * @param {string} eventName The event name to get property descriptor. | ||
|  |  * @returns {PropertyDescriptor} The property descriptor. | ||
|  |  * @private | ||
|  |  */ | ||
|  | function defineEventAttributeDescriptor(eventName) { | ||
|  |     return { | ||
|  |         get() { | ||
|  |             const listeners = getListeners(this); | ||
|  |             let node = listeners.get(eventName); | ||
|  |             while (node != null) { | ||
|  |                 if (node.listenerType === ATTRIBUTE) { | ||
|  |                     return node.listener | ||
|  |                 } | ||
|  |                 node = node.next; | ||
|  |             } | ||
|  |             return null | ||
|  |         }, | ||
|  | 
 | ||
|  |         set(listener) { | ||
|  |             if (typeof listener !== "function" && !isObject(listener)) { | ||
|  |                 listener = null; // eslint-disable-line no-param-reassign
 | ||
|  |             } | ||
|  |             const listeners = getListeners(this); | ||
|  | 
 | ||
|  |             // Traverse to the tail while removing old value.
 | ||
|  |             let prev = null; | ||
|  |             let node = listeners.get(eventName); | ||
|  |             while (node != null) { | ||
|  |                 if (node.listenerType === ATTRIBUTE) { | ||
|  |                     // Remove old value.
 | ||
|  |                     if (prev !== null) { | ||
|  |                         prev.next = node.next; | ||
|  |                     } else if (node.next !== null) { | ||
|  |                         listeners.set(eventName, node.next); | ||
|  |                     } else { | ||
|  |                         listeners.delete(eventName); | ||
|  |                     } | ||
|  |                 } else { | ||
|  |                     prev = node; | ||
|  |                 } | ||
|  | 
 | ||
|  |                 node = node.next; | ||
|  |             } | ||
|  | 
 | ||
|  |             // Add new value.
 | ||
|  |             if (listener !== null) { | ||
|  |                 const newNode = { | ||
|  |                     listener, | ||
|  |                     listenerType: ATTRIBUTE, | ||
|  |                     passive: false, | ||
|  |                     once: false, | ||
|  |                     next: null, | ||
|  |                 }; | ||
|  |                 if (prev === null) { | ||
|  |                     listeners.set(eventName, newNode); | ||
|  |                 } else { | ||
|  |                     prev.next = newNode; | ||
|  |                 } | ||
|  |             } | ||
|  |         }, | ||
|  |         configurable: true, | ||
|  |         enumerable: true, | ||
|  |     } | ||
|  | } | ||
|  | 
 | ||
|  | /** | ||
|  |  * Define an event attribute (e.g. `eventTarget.onclick`). | ||
|  |  * @param {Object} eventTargetPrototype The event target prototype to define an event attrbite. | ||
|  |  * @param {string} eventName The event name to define. | ||
|  |  * @returns {void} | ||
|  |  */ | ||
|  | function defineEventAttribute(eventTargetPrototype, eventName) { | ||
|  |     Object.defineProperty( | ||
|  |         eventTargetPrototype, | ||
|  |         `on${eventName}`, | ||
|  |         defineEventAttributeDescriptor(eventName) | ||
|  |     ); | ||
|  | } | ||
|  | 
 | ||
|  | /** | ||
|  |  * Define a custom EventTarget with event attributes. | ||
|  |  * @param {string[]} eventNames Event names for event attributes. | ||
|  |  * @returns {EventTarget} The custom EventTarget. | ||
|  |  * @private | ||
|  |  */ | ||
|  | function defineCustomEventTarget(eventNames) { | ||
|  |     /** CustomEventTarget */ | ||
|  |     function CustomEventTarget() { | ||
|  |         EventTarget.call(this); | ||
|  |     } | ||
|  | 
 | ||
|  |     CustomEventTarget.prototype = Object.create(EventTarget.prototype, { | ||
|  |         constructor: { | ||
|  |             value: CustomEventTarget, | ||
|  |             configurable: true, | ||
|  |             writable: true, | ||
|  |         }, | ||
|  |     }); | ||
|  | 
 | ||
|  |     for (let i = 0; i < eventNames.length; ++i) { | ||
|  |         defineEventAttribute(CustomEventTarget.prototype, eventNames[i]); | ||
|  |     } | ||
|  | 
 | ||
|  |     return CustomEventTarget | ||
|  | } | ||
|  | 
 | ||
|  | /** | ||
|  |  * EventTarget. | ||
|  |  * | ||
|  |  * - This is constructor if no arguments. | ||
|  |  * - This is a function which returns a CustomEventTarget constructor if there are arguments. | ||
|  |  * | ||
|  |  * For example: | ||
|  |  * | ||
|  |  *     class A extends EventTarget {} | ||
|  |  *     class B extends EventTarget("message") {} | ||
|  |  *     class C extends EventTarget("message", "error") {} | ||
|  |  *     class D extends EventTarget(["message", "error"]) {} | ||
|  |  */ | ||
|  | function EventTarget() { | ||
|  |     /*eslint-disable consistent-return */ | ||
|  |     if (this instanceof EventTarget) { | ||
|  |         listenersMap.set(this, new Map()); | ||
|  |         return | ||
|  |     } | ||
|  |     if (arguments.length === 1 && Array.isArray(arguments[0])) { | ||
|  |         return defineCustomEventTarget(arguments[0]) | ||
|  |     } | ||
|  |     if (arguments.length > 0) { | ||
|  |         const types = new Array(arguments.length); | ||
|  |         for (let i = 0; i < arguments.length; ++i) { | ||
|  |             types[i] = arguments[i]; | ||
|  |         } | ||
|  |         return defineCustomEventTarget(types) | ||
|  |     } | ||
|  |     throw new TypeError("Cannot call a class as a function") | ||
|  |     /*eslint-enable consistent-return */ | ||
|  | } | ||
|  | 
 | ||
|  | // Should be enumerable, but class methods are not enumerable.
 | ||
|  | EventTarget.prototype = { | ||
|  |     /** | ||
|  |      * Add a given listener to this event target. | ||
|  |      * @param {string} eventName The event name to add. | ||
|  |      * @param {Function} listener The listener to add. | ||
|  |      * @param {boolean|{capture?:boolean,passive?:boolean,once?:boolean}} [options] The options for this listener. | ||
|  |      * @returns {void} | ||
|  |      */ | ||
|  |     addEventListener(eventName, listener, options) { | ||
|  |         if (listener == null) { | ||
|  |             return | ||
|  |         } | ||
|  |         if (typeof listener !== "function" && !isObject(listener)) { | ||
|  |             throw new TypeError("'listener' should be a function or an object.") | ||
|  |         } | ||
|  | 
 | ||
|  |         const listeners = getListeners(this); | ||
|  |         const optionsIsObj = isObject(options); | ||
|  |         const capture = optionsIsObj | ||
|  |             ? Boolean(options.capture) | ||
|  |             : Boolean(options); | ||
|  |         const listenerType = capture ? CAPTURE : BUBBLE; | ||
|  |         const newNode = { | ||
|  |             listener, | ||
|  |             listenerType, | ||
|  |             passive: optionsIsObj && Boolean(options.passive), | ||
|  |             once: optionsIsObj && Boolean(options.once), | ||
|  |             next: null, | ||
|  |         }; | ||
|  | 
 | ||
|  |         // Set it as the first node if the first node is null.
 | ||
|  |         let node = listeners.get(eventName); | ||
|  |         if (node === undefined) { | ||
|  |             listeners.set(eventName, newNode); | ||
|  |             return | ||
|  |         } | ||
|  | 
 | ||
|  |         // Traverse to the tail while checking duplication..
 | ||
|  |         let prev = null; | ||
|  |         while (node != null) { | ||
|  |             if ( | ||
|  |                 node.listener === listener && | ||
|  |                 node.listenerType === listenerType | ||
|  |             ) { | ||
|  |                 // Should ignore duplication.
 | ||
|  |                 return | ||
|  |             } | ||
|  |             prev = node; | ||
|  |             node = node.next; | ||
|  |         } | ||
|  | 
 | ||
|  |         // Add it.
 | ||
|  |         prev.next = newNode; | ||
|  |     }, | ||
|  | 
 | ||
|  |     /** | ||
|  |      * Remove a given listener from this event target. | ||
|  |      * @param {string} eventName The event name to remove. | ||
|  |      * @param {Function} listener The listener to remove. | ||
|  |      * @param {boolean|{capture?:boolean,passive?:boolean,once?:boolean}} [options] The options for this listener. | ||
|  |      * @returns {void} | ||
|  |      */ | ||
|  |     removeEventListener(eventName, listener, options) { | ||
|  |         if (listener == null) { | ||
|  |             return | ||
|  |         } | ||
|  | 
 | ||
|  |         const listeners = getListeners(this); | ||
|  |         const capture = isObject(options) | ||
|  |             ? Boolean(options.capture) | ||
|  |             : Boolean(options); | ||
|  |         const listenerType = capture ? CAPTURE : BUBBLE; | ||
|  | 
 | ||
|  |         let prev = null; | ||
|  |         let node = listeners.get(eventName); | ||
|  |         while (node != null) { | ||
|  |             if ( | ||
|  |                 node.listener === listener && | ||
|  |                 node.listenerType === listenerType | ||
|  |             ) { | ||
|  |                 if (prev !== null) { | ||
|  |                     prev.next = node.next; | ||
|  |                 } else if (node.next !== null) { | ||
|  |                     listeners.set(eventName, node.next); | ||
|  |                 } else { | ||
|  |                     listeners.delete(eventName); | ||
|  |                 } | ||
|  |                 return | ||
|  |             } | ||
|  | 
 | ||
|  |             prev = node; | ||
|  |             node = node.next; | ||
|  |         } | ||
|  |     }, | ||
|  | 
 | ||
|  |     /** | ||
|  |      * Dispatch a given event. | ||
|  |      * @param {Event|{type:string}} event The event to dispatch. | ||
|  |      * @returns {boolean} `false` if canceled. | ||
|  |      */ | ||
|  |     dispatchEvent(event) { | ||
|  |         if (event == null || typeof event.type !== "string") { | ||
|  |             throw new TypeError('"event.type" should be a string.') | ||
|  |         } | ||
|  | 
 | ||
|  |         // If listeners aren't registered, terminate.
 | ||
|  |         const listeners = getListeners(this); | ||
|  |         const eventName = event.type; | ||
|  |         let node = listeners.get(eventName); | ||
|  |         if (node == null) { | ||
|  |             return true | ||
|  |         } | ||
|  | 
 | ||
|  |         // Since we cannot rewrite several properties, so wrap object.
 | ||
|  |         const wrappedEvent = wrapEvent(this, event); | ||
|  | 
 | ||
|  |         // This doesn't process capturing phase and bubbling phase.
 | ||
|  |         // This isn't participating in a tree.
 | ||
|  |         let prev = null; | ||
|  |         while (node != null) { | ||
|  |             // Remove this listener if it's once
 | ||
|  |             if (node.once) { | ||
|  |                 if (prev !== null) { | ||
|  |                     prev.next = node.next; | ||
|  |                 } else if (node.next !== null) { | ||
|  |                     listeners.set(eventName, node.next); | ||
|  |                 } else { | ||
|  |                     listeners.delete(eventName); | ||
|  |                 } | ||
|  |             } else { | ||
|  |                 prev = node; | ||
|  |             } | ||
|  | 
 | ||
|  |             // Call this listener
 | ||
|  |             setPassiveListener( | ||
|  |                 wrappedEvent, | ||
|  |                 node.passive ? node.listener : null | ||
|  |             ); | ||
|  |             if (typeof node.listener === "function") { | ||
|  |                 try { | ||
|  |                     node.listener.call(this, wrappedEvent); | ||
|  |                 } catch (err) { | ||
|  |                     if ( | ||
|  |                         typeof console !== "undefined" && | ||
|  |                         typeof console.error === "function" | ||
|  |                     ) { | ||
|  |                         console.error(err); | ||
|  |                     } | ||
|  |                 } | ||
|  |             } else if ( | ||
|  |                 node.listenerType !== ATTRIBUTE && | ||
|  |                 typeof node.listener.handleEvent === "function" | ||
|  |             ) { | ||
|  |                 node.listener.handleEvent(wrappedEvent); | ||
|  |             } | ||
|  | 
 | ||
|  |             // Break if `event.stopImmediatePropagation` was called.
 | ||
|  |             if (isStopped(wrappedEvent)) { | ||
|  |                 break | ||
|  |             } | ||
|  | 
 | ||
|  |             node = node.next; | ||
|  |         } | ||
|  |         setPassiveListener(wrappedEvent, null); | ||
|  |         setEventPhase(wrappedEvent, 0); | ||
|  |         setCurrentTarget(wrappedEvent, null); | ||
|  | 
 | ||
|  |         return !wrappedEvent.defaultPrevented | ||
|  |     }, | ||
|  | }; | ||
|  | 
 | ||
|  | // `constructor` is not enumerable.
 | ||
|  | Object.defineProperty(EventTarget.prototype, "constructor", { | ||
|  |     value: EventTarget, | ||
|  |     configurable: true, | ||
|  |     writable: true, | ||
|  | }); | ||
|  | 
 | ||
|  | // Ensure `eventTarget instanceof window.EventTarget` is `true`.
 | ||
|  | if ( | ||
|  |     typeof window !== "undefined" && | ||
|  |     typeof window.EventTarget !== "undefined" | ||
|  | ) { | ||
|  |     Object.setPrototypeOf(EventTarget.prototype, window.EventTarget.prototype); | ||
|  | } | ||
|  | 
 | ||
|  | export default EventTarget; | ||
|  | export { defineEventAttribute, EventTarget }; | ||
|  | //# sourceMappingURL=event-target-shim.mjs.map
 |