Spaces:
Runtime error
Runtime error
| /** | |
| * @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 | |