event-target-shim
Advanced tools
Comparing version 2.0.0 to 3.0.0
@@ -1,597 +0,756 @@ | ||
(function(f){if(typeof exports==="object"&&typeof module!=="undefined"){module.exports=f()}else if(typeof define==="function"&&define.amd){define([],f)}else{var g;if(typeof window!=="undefined"){g=window}else if(typeof global!=="undefined"){g=global}else if(typeof self!=="undefined"){g=self}else{g=this}g.eventTargetShim = f()}})(function(){var define,module,exports;return (function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o<r.length;o++)s(r[o]);return s})({1:[function(require,module,exports){ | ||
/** | ||
* @author Toru Nagashima | ||
* @author Toru Nagashima <https://github.com/mysticatea> | ||
* @copyright 2015 Toru Nagashima. All rights reserved. | ||
* See LICENSE file in root directory for full license. | ||
*/ | ||
"use strict" | ||
'use strict'; | ||
/** | ||
* Creates a unique key. | ||
* | ||
* @param {string} name - A name to create. | ||
* @returns {symbol|string} Generated unique key. | ||
* @private | ||
*/ | ||
var createUniqueKey = module.exports.createUniqueKey = ( | ||
typeof Symbol !== "undefined" ? Symbol : //eslint-disable-line no-undef | ||
/* otherwise */ function createUniqueKey(name) { | ||
return "[[" + name + "_" + Math.random().toFixed(8).slice(2) + "]]" | ||
} | ||
) | ||
Object.defineProperty(exports, '__esModule', { value: true }); | ||
/** | ||
* Checks whether the given value is a non-null object or not. | ||
* | ||
* @param {any} x - The value to be check. | ||
* @returns {boolean} `true` if the value is a non-null object. | ||
* @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 immediately. | ||
* @property {Function|null} passiveListener The listener if the current listener is passive. Otherwise this is null. | ||
* @property {number} timeStamp The unix time. | ||
* @private | ||
*/ | ||
var isObject = module.exports.isObject = function isObject(x) { | ||
return typeof x === "object" && x !== null | ||
} | ||
/** | ||
* The key of listeners. | ||
* | ||
* @type {symbol|string} | ||
* Private data for event wrappers. | ||
* @type {WeakMap<Event, PrivateData>} | ||
* @private | ||
*/ | ||
module.exports.LISTENERS = createUniqueKey("listeners") | ||
const privateData = new WeakMap(); | ||
/** | ||
* A value of kind for listeners which are registered in the capturing phase. | ||
* | ||
* @type {number} | ||
* Cache for wrapper classes. | ||
* @type {WeakMap<Object, Function>} | ||
* @private | ||
*/ | ||
module.exports.CAPTURE = 1 | ||
const wrappers = new WeakMap(); | ||
/** | ||
* A value of kind for listeners which are registered in the bubbling phase. | ||
* | ||
* @type {number} | ||
* Get private data. | ||
* @param {Event} event The event object to get private data. | ||
* @returns {PrivateData} The private data of the event. | ||
* @private | ||
*/ | ||
module.exports.BUBBLE = 2 | ||
function pd(event) { | ||
const retv = privateData.get(event); | ||
console.assert(retv != null, "'this' is expected an Event object, but got", event); | ||
return retv | ||
} | ||
/** | ||
* A value of kind for listeners which are registered as an attribute. | ||
* | ||
* @type {number} | ||
* @see https://dom.spec.whatwg.org/#interface-event | ||
* @private | ||
*/ | ||
module.exports.ATTRIBUTE = 3 | ||
/** | ||
* @typedef object ListenerNode | ||
* @property {function} listener - A listener function. | ||
* @property {number} kind - The kind of the listener. | ||
* @property {ListenerNode|null} next - The next node. | ||
* If this node is the last, this is `null`. | ||
* 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, | ||
passiveListener: null, | ||
timeStamp: event.timeStamp || Date.now(), | ||
}); | ||
/** | ||
* Creates a node of singly linked list for a list of listeners. | ||
* | ||
* @param {function} listener - A listener function. | ||
* @param {number} kind - The kind of the listener. | ||
* @param {object} [options] - The option object. | ||
* @param {boolean} [options.once] - The flag to remove the listener at the first call. | ||
* @param {boolean} [options.passive] - The flag to ignore `event.preventDefault` method. | ||
* @returns {ListenerNode} The created listener node. | ||
*/ | ||
module.exports.newNode = function newNode(listener, kind, options) { | ||
var obj = isObject(options) | ||
// https://heycam.github.io/webidl/#Unforgeable | ||
Object.defineProperty(this, "isTrusted", { value: false, enumerable: true }); | ||
return { | ||
listener: listener, | ||
kind: kind, | ||
once: obj && Boolean(options.once), | ||
passive: obj && Boolean(options.passive), | ||
next: null, | ||
// 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)); | ||
} | ||
} | ||
} | ||
},{}],2:[function(require,module,exports){ | ||
/** | ||
* @author Toru Nagashima | ||
* @copyright 2015 Toru Nagashima. All rights reserved. | ||
* See LICENSE file in root directory for full license. | ||
*/ | ||
"use strict" | ||
// 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 | ||
}, | ||
//----------------------------------------------------------------------------- | ||
// Requirements | ||
//----------------------------------------------------------------------------- | ||
/** | ||
* The target of this event. | ||
* @type {EventTarget} | ||
*/ | ||
get target() { | ||
return pd(this).eventTarget | ||
}, | ||
var Commons = require("./commons") | ||
var LISTENERS = Commons.LISTENERS | ||
var ATTRIBUTE = Commons.ATTRIBUTE | ||
var newNode = Commons.newNode | ||
/** | ||
* The target of this event. | ||
* @type {EventTarget} | ||
*/ | ||
get currentTarget() { | ||
return pd(this).currentTarget | ||
}, | ||
//----------------------------------------------------------------------------- | ||
// Helpers | ||
//----------------------------------------------------------------------------- | ||
/** | ||
* @returns {EventTarget[]} The composed path of this event. | ||
*/ | ||
composedPath() { | ||
const currentTarget = pd(this).currentTarget; | ||
if (currentTarget == null) { | ||
return [] | ||
} | ||
return [currentTarget] | ||
}, | ||
/** | ||
* Gets a specified attribute listener from a given EventTarget object. | ||
* | ||
* @param {EventTarget} eventTarget - An EventTarget object to get. | ||
* @param {string} type - An event type to get. | ||
* @returns {function|null} The found attribute listener. | ||
*/ | ||
function getAttributeListener(eventTarget, type) { | ||
var node = eventTarget[LISTENERS][type] | ||
while (node != null) { | ||
if (node.kind === ATTRIBUTE) { | ||
return node.listener | ||
/** | ||
* 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); | ||
if (typeof data.event.stopPropagation === "function") { | ||
data.event.stopPropagation(); | ||
} | ||
node = node.next | ||
} | ||
return null | ||
} | ||
}, | ||
/** | ||
* Sets a specified attribute listener to a given EventTarget object. | ||
* | ||
* @param {EventTarget} eventTarget - An EventTarget object to set. | ||
* @param {string} type - An event type to set. | ||
* @param {function|null} listener - A listener to be set. | ||
* @returns {void} | ||
*/ | ||
function setAttributeListener(eventTarget, type, listener) { | ||
if (typeof listener !== "function" && typeof listener !== "object") { | ||
listener = null // eslint-disable-line no-param-reassign | ||
} | ||
/** | ||
* Stop event bubbling. | ||
* @returns {void} | ||
*/ | ||
stopImmediatePropagation() { | ||
const data = pd(this); | ||
var prev = null | ||
var node = eventTarget[LISTENERS][type] | ||
while (node != null) { | ||
if (node.kind === ATTRIBUTE) { | ||
// Remove old value. | ||
if (prev == null) { | ||
eventTarget[LISTENERS][type] = node.next | ||
} | ||
else { | ||
prev.next = node.next | ||
} | ||
data.stopped = true; | ||
if (typeof data.event.stopImmediatePropagation === "function") { | ||
data.event.stopImmediatePropagation(); | ||
} | ||
else { | ||
prev = node | ||
} | ||
}, | ||
node = node.next | ||
} | ||
/** | ||
* The flag to be bubbling. | ||
* @type {boolean} | ||
*/ | ||
get bubbles() { | ||
return Boolean(pd(this).event.bubbles) | ||
}, | ||
// Add new value. | ||
if (listener != null) { | ||
if (prev == null) { | ||
eventTarget[LISTENERS][type] = newNode(listener, ATTRIBUTE) | ||
/** | ||
* The flag to be cancelable. | ||
* @type {boolean} | ||
*/ | ||
get cancelable() { | ||
return Boolean(pd(this).event.cancelable) | ||
}, | ||
/** | ||
* Cancel this event. | ||
* @returns {void} | ||
*/ | ||
preventDefault() { | ||
const data = pd(this); | ||
if (data.passiveListener != null) { | ||
console.warn("Event#preventDefault() was called from a passive listener:", data.passiveListener); | ||
return | ||
} | ||
else { | ||
prev.next = newNode(listener, ATTRIBUTE) | ||
if (!data.event.cancelable) { | ||
return | ||
} | ||
} | ||
} | ||
//----------------------------------------------------------------------------- | ||
// Public Interface | ||
//----------------------------------------------------------------------------- | ||
data.canceled = true; | ||
if (typeof data.event.preventDefault === "function") { | ||
data.event.preventDefault(); | ||
} | ||
}, | ||
/** | ||
* Defines an `EventTarget` implementation which has `onfoobar` attributes. | ||
* | ||
* @param {EventTarget} EventTargetBase - A base implementation of EventTarget. | ||
* @param {string[]} types - A list of event types which are defined as attribute listeners. | ||
* @returns {EventTarget} The defined `EventTarget` implementation which has attribute listeners. | ||
*/ | ||
module.exports.defineCustomEventTarget = function(EventTargetBase, types) { | ||
/** | ||
* The constructor of custom event target. | ||
* @constructor | ||
* The flag to indicate cancellation state. | ||
* @type {boolean} | ||
*/ | ||
function EventTarget() { | ||
EventTargetBase.call(this) | ||
} | ||
get defaultPrevented() { | ||
return pd(this).canceled | ||
}, | ||
var descripter = { | ||
constructor: { | ||
value: EventTarget, | ||
configurable: true, | ||
writable: true, | ||
}, | ||
} | ||
/** | ||
* The flag to be composed. | ||
* @type {boolean} | ||
*/ | ||
get composed() { | ||
return Boolean(pd(this).event.composed) | ||
}, | ||
types.forEach(function(type) { | ||
descripter["on" + type] = { | ||
get: function() { | ||
return getAttributeListener(this, type) | ||
}, | ||
set: function(listener) { | ||
setAttributeListener(this, type, listener) | ||
}, | ||
configurable: true, | ||
enumerable: true, | ||
} | ||
}) | ||
/** | ||
* The unix time of this event. | ||
* @type {number} | ||
*/ | ||
get timeStamp() { | ||
return pd(this).timeStamp | ||
}, | ||
}; | ||
EventTarget.prototype = Object.create(EventTargetBase.prototype, descripter) | ||
// `constructor` is not enumerable. | ||
Object.defineProperty(Event.prototype, "constructor", { value: Event, configurable: true, writable: true }); | ||
return EventTarget | ||
// 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); | ||
} | ||
},{"./commons":1}],3:[function(require,module,exports){ | ||
/** | ||
* @author Toru Nagashima | ||
* @copyright 2015 Toru Nagashima. All rights reserved. | ||
* See LICENSE file in root directory for full license. | ||
* 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 | ||
*/ | ||
"use strict" | ||
function defineRedirectDescriptor(key) { | ||
return { | ||
get() { | ||
return pd(this).event[key] | ||
}, | ||
set(value) { | ||
pd(this).event[key] = value; | ||
}, | ||
configurable: true, | ||
enumerable: true, | ||
} | ||
} | ||
//------------------------------------------------------------------------------ | ||
// Requirements | ||
//------------------------------------------------------------------------------ | ||
/*globals window */ | ||
var Commons = require("./commons") | ||
var CustomEventTarget = require("./custom-event-target") | ||
var EventWrapper = require("./event-wrapper") | ||
var isObject = Commons.isObject | ||
var LISTENERS = Commons.LISTENERS | ||
var CAPTURE = Commons.CAPTURE | ||
var BUBBLE = Commons.BUBBLE | ||
var ATTRIBUTE = Commons.ATTRIBUTE | ||
var newNode = Commons.newNode | ||
var defineCustomEventTarget = CustomEventTarget.defineCustomEventTarget | ||
var createEventWrapper = EventWrapper.createEventWrapper | ||
var STOP_IMMEDIATE_PROPAGATION_FLAG = EventWrapper.STOP_IMMEDIATE_PROPAGATION_FLAG | ||
var PASSIVE_LISTENER_FLAG = EventWrapper.PASSIVE_LISTENER_FLAG | ||
//------------------------------------------------------------------------------ | ||
// Constants | ||
//------------------------------------------------------------------------------ | ||
/** | ||
* A flag which shows there is the native `EventTarget` interface object. | ||
* | ||
* @type {boolean} | ||
* 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 | ||
*/ | ||
var HAS_EVENTTARGET_INTERFACE = ( | ||
typeof window !== "undefined" && | ||
typeof window.EventTarget !== "undefined" | ||
) | ||
function defineCallDescriptor(key) { | ||
return { | ||
value() { | ||
const event = pd(this).event; | ||
return event[key].apply(event, arguments) | ||
}, | ||
configurable: true, | ||
enumerable: true, | ||
} | ||
} | ||
//------------------------------------------------------------------------------ | ||
// Public Interface | ||
//------------------------------------------------------------------------------ | ||
/** | ||
* An implementation for `EventTarget` interface. | ||
* | ||
* @constructor | ||
* @public | ||
* 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 | ||
*/ | ||
var EventTarget = module.exports = function EventTarget() { | ||
if (this instanceof EventTarget) { | ||
// this[LISTENERS] is a Map. | ||
// Its key is event type. | ||
// Its value is ListenerNode object or null. | ||
// | ||
// interface ListenerNode { | ||
// var listener: Function | ||
// var kind: CAPTURE|BUBBLE|ATTRIBUTE | ||
// var next: ListenerNode|null | ||
// } | ||
Object.defineProperty(this, LISTENERS, {value: Object.create(null)}) | ||
function defineWrapper(BaseEvent, proto) { | ||
const keys = Object.keys(proto); | ||
if (keys.length === 0) { | ||
return BaseEvent | ||
} | ||
else if (arguments.length === 1 && Array.isArray(arguments[0])) { | ||
return defineCustomEventTarget(EventTarget, arguments[0]) | ||
} | ||
else if (arguments.length > 0) { | ||
var types = Array(arguments.length) | ||
for (var i = 0; i < arguments.length; ++i) { | ||
types[i] = arguments[i] | ||
} | ||
// To use to extend with attribute listener properties. | ||
// e.g. | ||
// class MyCustomObject extends EventTarget("message", "error") { | ||
// //... | ||
// } | ||
return defineCustomEventTarget(EventTarget, types) | ||
/** CustomEvent */ | ||
function CustomEvent(eventTarget, event) { | ||
BaseEvent.call(this, eventTarget, event); | ||
} | ||
else { | ||
throw new TypeError("Cannot call a class as a function") | ||
} | ||
} | ||
EventTarget.prototype = Object.create( | ||
(HAS_EVENTTARGET_INTERFACE ? window.EventTarget : Object).prototype, | ||
{ | ||
constructor: { | ||
value: EventTarget, | ||
writable: true, | ||
configurable: true, | ||
}, | ||
CustomEvent.prototype = Object.create(BaseEvent.prototype, { | ||
constructor: { value: CustomEvent, configurable: true, writable: true }, | ||
}); | ||
addEventListener: { | ||
value: function addEventListener(type, listener, options) { | ||
if (listener == null) { | ||
return false | ||
} | ||
if (typeof listener !== "function" && typeof listener !== "object") { | ||
throw new TypeError("\"listener\" is not an object.") | ||
} | ||
// 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) | ||
); | ||
} | ||
} | ||
var capture = isObject(options) ? Boolean(options.capture) : Boolean(options) | ||
var kind = (capture ? CAPTURE : BUBBLE) | ||
var node = this[LISTENERS][type] | ||
if (node == null) { | ||
this[LISTENERS][type] = newNode(listener, kind, options) | ||
return true | ||
} | ||
return CustomEvent | ||
} | ||
var prev = null | ||
while (node != null) { | ||
if (node.listener === listener && node.kind === kind) { | ||
// Should ignore a duplicated listener. | ||
return false | ||
} | ||
prev = node | ||
node = node.next | ||
} | ||
/** | ||
* 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 | ||
} | ||
prev.next = newNode(listener, kind, options) | ||
return true | ||
}, | ||
configurable: true, | ||
writable: true, | ||
}, | ||
removeEventListener: { | ||
value: function removeEventListener(type, listener, options) { | ||
if (listener == null) { | ||
return false | ||
} | ||
var capture = isObject(options) ? Boolean(options.capture) : Boolean(options) | ||
var kind = (capture ? CAPTURE : BUBBLE) | ||
var prev = null | ||
var node = this[LISTENERS][type] | ||
while (node != null) { | ||
if (node.listener === listener && node.kind === kind) { | ||
if (prev == null) { | ||
this[LISTENERS][type] = node.next | ||
} | ||
else { | ||
prev.next = node.next | ||
} | ||
return true | ||
} | ||
prev = node | ||
node = node.next | ||
} | ||
return false | ||
}, | ||
configurable: true, | ||
writable: true, | ||
}, | ||
dispatchEvent: { | ||
value: function dispatchEvent(event) { | ||
// If listeners aren't registered, terminate. | ||
var type = event.type | ||
var node = this[LISTENERS][type] | ||
if (node == null) { | ||
return true | ||
} | ||
// Since we cannot rewrite several properties, so wrap object. | ||
var wrapped = createEventWrapper(event, this) | ||
// This doesn't process capturing phase and bubbling phase. | ||
// This isn't participating in a tree. | ||
var prev = null | ||
while (node != null) { | ||
// Remove this listener if it's once | ||
if (node.once) { | ||
if (prev == null) { | ||
this[LISTENERS][type] = node.next | ||
} | ||
else { | ||
prev.next = node.next | ||
} | ||
} | ||
else { | ||
prev = node | ||
} | ||
// Call this listener | ||
wrapped[PASSIVE_LISTENER_FLAG] = node.passive | ||
if (typeof node.listener === "function") { | ||
node.listener.call(this, wrapped) | ||
} | ||
else if (node.kind !== ATTRIBUTE && typeof node.listener.handleEvent === "function") { | ||
node.listener.handleEvent(wrapped) | ||
} | ||
// Break if `event.stopImmediatePropagation` was called. | ||
if (wrapped[STOP_IMMEDIATE_PROPAGATION_FLAG]) { | ||
break | ||
} | ||
node = node.next | ||
} | ||
return !wrapped.defaultPrevented | ||
}, | ||
configurable: true, | ||
writable: true, | ||
}, | ||
let wrapper = wrappers.get(proto); | ||
if (wrapper == null) { | ||
wrapper = defineWrapper(getWrapper(Object.getPrototypeOf(proto)), proto); | ||
wrappers.set(proto, wrapper); | ||
} | ||
) | ||
return wrapper | ||
} | ||
},{"./commons":1,"./custom-event-target":2,"./event-wrapper":4}],4:[function(require,module,exports){ | ||
/** | ||
* @author Toru Nagashima | ||
* @copyright 2015 Toru Nagashima. All rights reserved. | ||
* See LICENSE file in root directory for full license. | ||
* 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 | ||
*/ | ||
"use strict" | ||
function wrapEvent(eventTarget, event) { | ||
const Wrapper = getWrapper(Object.getPrototypeOf(event)); | ||
return new Wrapper(eventTarget, event) | ||
} | ||
//----------------------------------------------------------------------------- | ||
// Requirements | ||
//----------------------------------------------------------------------------- | ||
/** | ||
* Get the stopped 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).stopped | ||
} | ||
var createUniqueKey = require("./commons").createUniqueKey | ||
/** | ||
* 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; | ||
} | ||
//----------------------------------------------------------------------------- | ||
// Constsnts | ||
//----------------------------------------------------------------------------- | ||
/** | ||
* 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; | ||
} | ||
/** | ||
* The key of the flag which is turned on by `stopImmediatePropagation` method. | ||
* | ||
* @type {symbol|string} | ||
* 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 | ||
*/ | ||
var STOP_IMMEDIATE_PROPAGATION_FLAG = | ||
createUniqueKey("stop_immediate_propagation_flag") | ||
function setPassiveListener(event, passiveListener) { | ||
pd(event).passiveListener = passiveListener; | ||
} | ||
/** | ||
* The key of the flag which is turned on by `preventDefault` method. | ||
* | ||
* @type {symbol|string} | ||
* @typedef {object} ListenerNode | ||
* @property {Function} listener | ||
* @property {1|2|3} listenerType | ||
* @property {boolean} passive | ||
* @property {boolean} once | ||
* @property {ListenerNode|null} next | ||
* @private | ||
*/ | ||
var CANCELED_FLAG = createUniqueKey("canceled_flag") | ||
/** | ||
* The key of the flag that it cannot use `preventDefault` method. | ||
* | ||
* @type {symbol|string} | ||
* @type {WeakMap<object, Map<string, ListenerNode>>} | ||
* @private | ||
*/ | ||
var PASSIVE_LISTENER_FLAG = createUniqueKey("passive_listener_flag") | ||
const listenersMap = new WeakMap(); | ||
// Listener types | ||
const CAPTURE = 1; | ||
const BUBBLE = 2; | ||
const ATTRIBUTE = 3; | ||
/** | ||
* The key of the original event object. | ||
* | ||
* @type {symbol|string} | ||
* 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 | ||
*/ | ||
var ORIGINAL_EVENT = createUniqueKey("original_event") | ||
function getListeners(eventTarget) { | ||
const listeners = listenersMap.get(eventTarget); | ||
console.assert(listeners != null, "'this' is expected an EventTarget object, but got", eventTarget); | ||
return listeners || new Map() | ||
} | ||
/** | ||
* Method definitions for the event wrapper. | ||
* | ||
* @type {object} | ||
* 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 | ||
*/ | ||
var wrapperPrototypeDefinition = Object.freeze({ | ||
stopPropagation: Object.freeze({ | ||
value: function stopPropagation() { | ||
var e = this[ORIGINAL_EVENT] | ||
if (typeof e.stopPropagation === "function") { | ||
e.stopPropagation() | ||
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 | ||
}, | ||
writable: true, | ||
configurable: true, | ||
}), | ||
stopImmediatePropagation: Object.freeze({ | ||
value: function stopImmediatePropagation() { | ||
this[STOP_IMMEDIATE_PROPAGATION_FLAG] = true | ||
var e = this[ORIGINAL_EVENT] | ||
if (typeof e.stopImmediatePropagation === "function") { | ||
e.stopImmediatePropagation() | ||
set(listener) { | ||
if (typeof listener !== "function" && !isObject(listener)) { | ||
listener = null; // eslint-disable-line no-param-reassign | ||
} | ||
}, | ||
writable: true, | ||
configurable: true, | ||
}), | ||
const listeners = getListeners(this); | ||
preventDefault: Object.freeze({ | ||
value: function preventDefault() { | ||
if (this[PASSIVE_LISTENER_FLAG]) { | ||
return | ||
// 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; | ||
} | ||
if (this.cancelable === true) { | ||
this[CANCELED_FLAG] = true | ||
} | ||
var e = this[ORIGINAL_EVENT] | ||
if (typeof e.preventDefault === "function") { | ||
e.preventDefault() | ||
// 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; | ||
} | ||
} | ||
}, | ||
writable: true, | ||
configurable: true, | ||
}), | ||
defaultPrevented: Object.freeze({ | ||
get: function defaultPrevented() { | ||
return this[CANCELED_FLAG] | ||
}, | ||
enumerable: true, | ||
configurable: true, | ||
}), | ||
}) | ||
} | ||
} | ||
//----------------------------------------------------------------------------- | ||
// Public Interface | ||
//----------------------------------------------------------------------------- | ||
/** | ||
* 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)); | ||
} | ||
module.exports.STOP_IMMEDIATE_PROPAGATION_FLAG = STOP_IMMEDIATE_PROPAGATION_FLAG | ||
module.exports.PASSIVE_LISTENER_FLAG = PASSIVE_LISTENER_FLAG | ||
/** | ||
* Creates an event wrapper. | ||
* | ||
* We cannot modify several properties of `Event` object, so we need to create the wrapper. | ||
* Plus, this wrapper supports non `Event` objects. | ||
* | ||
* @param {Event|{type: string}} event - An original event to create the wrapper. | ||
* @param {EventTarget} eventTarget - The event target of the event. | ||
* @returns {Event} The created wrapper. This object is implemented `Event` interface. | ||
* Define a custom EventTarget with event attributes. | ||
* @param {string[]} eventNames Event names for event attributes. | ||
* @returns {EventTarget} The custom EventTarget. | ||
* @private | ||
*/ | ||
module.exports.createEventWrapper = function createEventWrapper(event, eventTarget) { | ||
var timeStamp = ( | ||
typeof event.timeStamp === "number" ? event.timeStamp : Date.now() | ||
) | ||
var propertyDefinition = { | ||
type: {value: event.type, enumerable: true}, | ||
target: {value: eventTarget, enumerable: true}, | ||
currentTarget: {value: eventTarget, enumerable: true}, | ||
eventPhase: {value: 2, enumerable: true}, | ||
bubbles: {value: Boolean(event.bubbles), enumerable: true}, | ||
cancelable: {value: Boolean(event.cancelable), enumerable: true}, | ||
timeStamp: {value: timeStamp, enumerable: true}, | ||
isTrusted: {value: false, enumerable: true}, | ||
function defineCustomEventTarget(eventNames) { | ||
/** CustomEventTarget */ | ||
function CustomEventTarget() { | ||
EventTarget.call(this); | ||
} | ||
propertyDefinition[STOP_IMMEDIATE_PROPAGATION_FLAG] = {value: false, writable: true} | ||
propertyDefinition[CANCELED_FLAG] = {value: false, writable: true} | ||
propertyDefinition[PASSIVE_LISTENER_FLAG] = {value: false, writable: true} | ||
propertyDefinition[ORIGINAL_EVENT] = {value: event} | ||
// For CustomEvent. | ||
if (typeof event.detail !== "undefined") { | ||
propertyDefinition.detail = {value: event.detail, enumerable: true} | ||
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 Object.create( | ||
Object.create(event, wrapperPrototypeDefinition), | ||
propertyDefinition | ||
) | ||
return CustomEventTarget | ||
} | ||
},{"./commons":1}]},{},[3])(3) | ||
}); | ||
/** | ||
* 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 {boolean} `true` if the listener was added actually. | ||
*/ | ||
addEventListener(eventName, listener, options) { | ||
if (listener == null) { | ||
return false | ||
} | ||
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 true | ||
} | ||
// Traverse to the tail while checking duplication.. | ||
let prev = null; | ||
while (node != null) { | ||
if (node.listener === listener && node.listenerType === listenerType) { | ||
// Should ignore duplication. | ||
return false | ||
} | ||
prev = node; | ||
node = node.next; | ||
} | ||
// Add it. | ||
prev.next = newNode; | ||
return true | ||
}, | ||
/** | ||
* 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 {boolean} `true` if the listener was removed actually. | ||
*/ | ||
removeEventListener(eventName, listener, options) { | ||
if (listener == null) { | ||
return false | ||
} | ||
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 true | ||
} | ||
prev = node; | ||
node = node.next; | ||
} | ||
return false | ||
}, | ||
/** | ||
* 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") { | ||
node.listener.call(this, wrappedEvent); | ||
} | ||
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); | ||
} | ||
exports.defineEventAttribute = defineEventAttribute; | ||
exports.EventTarget = EventTarget; | ||
exports['default'] = EventTarget; | ||
module.exports = EventTarget | ||
module.exports.EventTarget = module.exports["default"] = EventTarget | ||
module.exports.defineEventAttribute = defineEventAttribute | ||
//# sourceMappingURL=event-target-shim.js.map |
{ | ||
"name": "event-target-shim", | ||
"version": "2.0.0", | ||
"version": "3.0.0", | ||
"description": "An implementation of W3C EventTarget interface.", | ||
"main": "lib/event-target.js", | ||
"main": "dist/event-target-shim", | ||
"files": [ | ||
"dist", | ||
"lib" | ||
"dist" | ||
], | ||
"engines": { | ||
"code": ">=4" | ||
}, | ||
"scripts": { | ||
@@ -14,25 +16,28 @@ "preversion": "npm test", | ||
"postversion": "git push && git push --tags", | ||
"clean": "rimraf coverage", | ||
"lint": "if-node-version \">=4\" eslint lib test", | ||
"build": "rimraf dist && run-s build:*", | ||
"build:dist": "mkdirp dist && browserify lib/event-target.js --standalone event-target-shim > dist/event-target-shim.js", | ||
"build:dist-min": "uglifyjs dist/event-target-shim.js --compress --mangle > dist/event-target-shim.min.js", | ||
"test": "run-s clean lint && karma start karma.conf.js --single-run", | ||
"watch": "karma start karma.conf.js --watch", | ||
"travis": "istanbul cover node_modules/mocha/bin/_mocha --report lcovonly -- test/**/*.js --require scripts/power-assert", | ||
"clean": "rimraf .nyc_output coverage", | ||
"coverage": "nyc report --reporter lcov && opener coverage/lcov-report/index.html", | ||
"lint": "eslint src test scripts", | ||
"build": "run-p build:*", | ||
"build:cjs": "node scripts/build-cjs.js", | ||
"build:mjs": "node scripts/build-mjs.js", | ||
"build:umd": "node scripts/build-umd.js", | ||
"pretest": "npm run lint", | ||
"test": "run-s test:*", | ||
"test:mocha": "nyc --require ./scripts/babel-register.js mocha test/index.mjs", | ||
"test:karma": "karma start scripts/karma.conf.js --single-run", | ||
"watch": "run-p watch:*", | ||
"watch:mocha": "mocha test/index.mjs --compilers mjs:babel-register --watch --growl", | ||
"watch:karma": "karma start scripts/karma.conf.js --watch", | ||
"codecov": "codecov" | ||
}, | ||
"devDependencies": { | ||
"browserify": "^13.1.1", | ||
"browserify-istanbul": "^2.0.0", | ||
"codecov": "^1.0.1", | ||
"eslint": "^3.11.0", | ||
"eslint-config-mysticatea": "^7.0.1", | ||
"espower-loader": "^1.0.0", | ||
"espowerify": "^1.0.0", | ||
"if-node-version": "^1.1.1", | ||
"istanbul": "^0.4.1", | ||
"karma": "^1.3.0", | ||
"karma-browserify": "^5.1.0", | ||
"karma-chrome-launcher": "^2.0.0", | ||
"babel-core": "^6.26.0", | ||
"babel-preset-env": "^1.6.0", | ||
"babel-register": "^6.26.0", | ||
"chai": "^4.1.2", | ||
"codecov": "^2.3.0", | ||
"eslint": "^4.7.2", | ||
"eslint-config-mysticatea": "^12.0.0", | ||
"karma": "^1.7.1", | ||
"karma-chrome-launcher": "^2.2.0", | ||
"karma-coverage": "^1.1.1", | ||
@@ -43,10 +48,16 @@ "karma-firefox-launcher": "^1.0.0", | ||
"karma-mocha": "^1.3.0", | ||
"mkdirp": "^0.5.1", | ||
"mocha": "^3.2.0", | ||
"npm-run-all": "^3.1.1", | ||
"power-assert": "^1.2.0", | ||
"rimraf": "^2.3.4", | ||
"spy": "^0.1.3", | ||
"uglify-js": "^2.6.1", | ||
"watchify": "^3.7.0" | ||
"karma-rollup-preprocessor": "^5.0.1", | ||
"mocha": "^3.5.3", | ||
"npm-run-all": "^4.1.1", | ||
"nyc": "^11.2.1", | ||
"opener": "^1.4.3", | ||
"rimraf": "^2.6.2", | ||
"rollup": "^0.50.0", | ||
"rollup-plugin-babel": "^3.0.2", | ||
"rollup-plugin-babel-minify": "^3.1.2", | ||
"rollup-plugin-commonjs": "^8.2.1", | ||
"rollup-plugin-json": "^2.3.0", | ||
"rollup-plugin-node-resolve": "^3.0.0", | ||
"rollup-watch": "^4.3.1", | ||
"spy": "^1.0.0" | ||
}, | ||
@@ -53,0 +64,0 @@ "repository": { |
286
README.md
@@ -15,177 +15,201 @@ # event-target-shim | ||
```js | ||
// The prototype of this class has getters and setters of `onmessage` and `onerror`. | ||
class Foo extends EventTarget("message", "error") { | ||
//... | ||
import {EventTarget, defineEventAttribute} from "event-target-shim" | ||
class Foo extends EventTarget { | ||
// ... | ||
} | ||
// Define `foo.onhello` property. | ||
defineEventAttribute(Foo.prototype, "hello") | ||
// Use | ||
const foo = new Foo() | ||
foo.addEventListener("hello", e => console.log("hello", e)) | ||
foo.onhello = e => console.log("onhello:", e) | ||
foo.dispatchEvent(new CustomEvent("hello")) | ||
``` | ||
## Installation | ||
## 💿 Installation | ||
Use [npm](https://www.npmjs.com/) to install then use a bundler. | ||
``` | ||
npm install --save event-target-shim | ||
npm install event-target-shim | ||
``` | ||
Or download from `dist` directory. | ||
Or download from [`dist` directory](./dist). | ||
## Usage | ||
- [dist/event-target-shim.mjs](dist/event-target-shim.mjs) ... ES modules version. | ||
- [dist/event-target-shim.js](dist/event-target-shim.js) ... Common JS version. | ||
- [dist/event-target-shim.umd.js](dist/event-target-shim.umd.js) ... UMD (Universal Module Definition) version. This is transpiled by [Babel](https://babeljs.io/) for IE 11. | ||
### Basic | ||
## 📖 Usage | ||
```js | ||
//----------------------------------------------------------------------------- | ||
// import (with browserify, webpack, etc...). | ||
const EventTarget = require("event-target-shim"); | ||
import {EventTarget, defineEventAttribute} from "event-target-shim" | ||
// or | ||
const {EventTarget, defineEventAttribute} = require("event-target-shim") | ||
//----------------------------------------------------------------------------- | ||
// define a custom type. | ||
class Foo extends EventTarget { | ||
} | ||
// or UMD version defines a global variable: | ||
const {EventTarget, defineEventAttribute} = window.EventTargetShim | ||
``` | ||
//----------------------------------------------------------------------------- | ||
// add event listeners. | ||
let foo = new Foo(); | ||
foo.addEventListener("foo", event => { | ||
console.log(event.hello); | ||
}); | ||
foo.addEventListener("foo", event => { | ||
if (event.hello !== "hello") { | ||
// event implements Event interface. | ||
event.preventDefault(); | ||
} | ||
}); | ||
### EventTarget | ||
//----------------------------------------------------------------------------- | ||
// dispatch an event. | ||
let event = document.createEvent("CustomEvent"); | ||
event.initCustomEvent("foo", /*bubbles*/ false, /*cancelable*/ false, /*detail*/ null); | ||
event.hello = "hello"; | ||
foo.dispatchEvent(event); | ||
> https://dom.spec.whatwg.org/#interface-eventtarget | ||
//----------------------------------------------------------------------------- | ||
// dispatch an event simply (non standard). | ||
foo.dispatchEvent({type: "foo", hello: "hello"}); | ||
#### eventTarget.addEventListener(type, callback, options) | ||
//----------------------------------------------------------------------------- | ||
// dispatch a cancelable event. | ||
if (!foo.dispatchEvent({type: "foo", cancelable: true, hello: "hey"})) { | ||
console.log("defaultPrevented"); | ||
} | ||
Register an event listener. | ||
//----------------------------------------------------------------------------- | ||
// If `window.EventTarget` exists, `EventTarget` inherits from `window.EventTarget`. | ||
if (foo instanceof window.EventTarget) { | ||
console.log("yay!"); | ||
} | ||
``` | ||
- `type` is a string. This is the event name to register. | ||
- `callback` is a function. This is the event listener to register. | ||
- `options` is a boolean or an object `{ capture?: boolean, passive?: boolean, once?: boolean }`. If this is a boolean, it's same meaning as `{ capture: options }`. | ||
- `capture` is the flag to register the event listener for capture phase. | ||
- `passive` is the flag to ignore `event.preventDefault()` method in the event listener. | ||
- `once` is the flag to remove the event listener automatically after the first call. | ||
### The Extension for Attribute Listeners | ||
#### eventTarget.removeEventListener(type, callback, options) | ||
```js | ||
//----------------------------------------------------------------------------- | ||
// import (with browserify, webpack, etc...). | ||
const EventTarget = require("event-target-shim"); | ||
Unregister an event listener. | ||
//----------------------------------------------------------------------------- | ||
// define a custom type with attribute listeners. | ||
class Foo extends EventTarget("message", "error") { | ||
} | ||
// or non-variadic | ||
class Foo extends EventTarget(["message", "error"]) { | ||
} | ||
- `type` is a string. This is the event name to unregister. | ||
- `callback` is a function. This is the event listener to unregister. | ||
- `options` is a boolean or an object `{ capture?: boolean }`. If this is a boolean, it's same meaning as `{ capture: options }`. | ||
- `capture` is the flag to register the event listener for capture phase. | ||
//----------------------------------------------------------------------------- | ||
// add event listeners. | ||
let foo = new Foo(); | ||
foo.onmessage = event => { | ||
console.log(event.data); | ||
}; | ||
foo.onerror = event => { | ||
console.log(event.message); | ||
}; | ||
foo.addEventListener("message", event => { | ||
console.log(event.data); | ||
}); | ||
#### eventTarget.dispatchEvent(event) | ||
//----------------------------------------------------------------------------- | ||
// dispatch a event simply (non standard). | ||
foo.dispatchEvent({type: "message", data: "hello"}); | ||
foo.dispatchEvent({type: "error", message: "an error"}); | ||
``` | ||
Dispatch an event. | ||
### Use in ES5 | ||
- `event` is a [Event](https://dom.spec.whatwg.org/#event) object or an object `{ type: string, [key: string]: any }`. The latter is non-standard but useful. In both cases, listeners receive the event as implementing [Event](https://dom.spec.whatwg.org/#event) interface. | ||
- Basic. | ||
### defineEventAttribute(proto, type) | ||
```js | ||
function Foo() { | ||
EventTarget.call(this); | ||
} | ||
Define an event attribute (e.g. `onclick`) to `proto`. This is non-standard. | ||
Foo.prototype = Object.create(EventTarget.prototype, { | ||
constructor: { | ||
value: Foo, | ||
configurable: true, | ||
writable: true | ||
}, | ||
- `proto` is an object (assuming it's a prototype object). This function defines a getter/setter pair for the event attribute. | ||
- `type` is a string. This is the event name to define. | ||
//.... | ||
}); | ||
``` | ||
For example: | ||
- With attribute listeners. | ||
```js | ||
class AbortSignal extends EventTarget { | ||
constructor() { | ||
this.aborted = false | ||
} | ||
} | ||
// Define `onabort` property. | ||
defineEventAttribute(AbortSignal.prototype, "abort") | ||
``` | ||
```js | ||
function Foo() { | ||
EventTarget.call(this); | ||
} | ||
### EventTarget(types) | ||
Foo.prototype = Object.create(EventTarget("message", "error").prototype, { | ||
// or | ||
// Foo.prototype = Object.create(EventTarget(["message", "error"]).prototype, { | ||
constructor: { | ||
value: Foo, | ||
configurable: true, | ||
writable: true | ||
}, | ||
Define a custom `EventTarget` class with event attributes. This is non-standard. | ||
//.... | ||
}); | ||
``` | ||
- `types` is a string or an array of strings. This is the event name to define. | ||
### Use with RequireJS | ||
For example: | ||
```js | ||
require(["https://cdn.rawgit.com/mysticatea/event-target-shim/v2.0.0/dist/event-target-shim.min.js"], function(EventTarget) { | ||
//... | ||
}); | ||
// This has `onabort` property. | ||
class AbortSignal extends EventTarget("abort") { | ||
constructor() { | ||
this.aborted = false | ||
} | ||
} | ||
``` | ||
## API | ||
## 📚 Examples | ||
```ts | ||
declare class EventTarget { | ||
constructor(); | ||
addEventListener(type: string, listener?: (event: Event) => void, options?: boolean | AddEventListenerOptions): void; | ||
removeEventListener(type: string, listener?: (event: Event) => void, options?: boolean | EventListenerOptions): void; | ||
dispatchEvent(event: Event | EventLike): boolean; | ||
### ES2015 and later | ||
> https://jsfiddle.net/636vea92/ | ||
```js | ||
const {EventTarget, defineEventAttribute} = EventTargetShim | ||
// Define a derived class. | ||
class Foo extends EventTarget { | ||
// ... | ||
} | ||
// Define EventTarget type with attribute listeners. | ||
declare function EventTarget(...types: string[]): EventTarget; | ||
declare function EventTarget(types: string[]): EventTarget; | ||
// Define `foo.onhello` property. | ||
defineEventAttribute(Foo.prototype, "hello") | ||
// Options | ||
interface EventListenerOptions { | ||
capture?: boolean; | ||
// Register event listeners. | ||
const foo = new Foo() | ||
foo.addEventListener("hello", (e) => { | ||
console.log("hello", e) | ||
}) | ||
foo.onhello = (e) => { | ||
console.log("onhello", e) | ||
} | ||
interface AddEventListenerOptions extends EventListenerOptions { | ||
once?: boolean; | ||
passive?: boolean; | ||
// Dispatching events | ||
foo.dispatchEvent(new CustomEvent("hello", { detail: "detail" })) | ||
``` | ||
### ES5 | ||
> https://jsfiddle.net/522zc9de/ | ||
```js | ||
// Define a derived class. | ||
function Foo() { | ||
EventTarget.call(this) | ||
} | ||
Foo.prototype = Object.create(EventTarget.prototype, { | ||
constructor: { value: Foo, configurable: true, writable: true } | ||
// ... | ||
}) | ||
// Non-standard. | ||
interface EventLike { | ||
type: string; | ||
cancelable?: boolean; | ||
// Define `foo.onhello` property. | ||
defineEventAttribute(Foo.prototype, "hello") | ||
// Register event listeners. | ||
var foo = new Foo() | ||
foo.addEventListener("hello", function(e) { | ||
console.log("hello", e) | ||
}) | ||
foo.onhello = function(e) { | ||
console.log("onhello", e) | ||
} | ||
// Dispatching events | ||
function isSupportEventConstrucor() { // IE does not support. | ||
try { | ||
new CusomEvent("hello") | ||
return true | ||
} catch (_err) { | ||
return false | ||
} | ||
} | ||
if (isSupportEventConstrucor()) { | ||
foo.dispatchEvent(new CustomEvent("hello", { detail: "detail" })) | ||
} else { | ||
var e = document.createEvent("CustomEvent") | ||
e.initCustomEvent("hello", false, false, "detail") | ||
foo.dispatchEvent(e) | ||
} | ||
``` | ||
## 📰 Changelog | ||
- See [GitHub releases](https://github.com/mysticatea/event-target-shim/releases). | ||
## 🍻 Contributing | ||
Contributing is welcome ❤️ | ||
Please use GitHub issues/PRs. | ||
### Development tools | ||
- `npm install` installs dependencies for development. | ||
- `npm test` runs tests and measures code coverage. | ||
- `npm run clean` removes temporary files of tests. | ||
- `npm run coverage` opens code coverage of the previous test with your default browser. | ||
- `npm run lint` runs ESLint. | ||
- `npm run build` generates `dist` codes. | ||
- `npm run watch` runs tests on each file change. |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Dynamic require
Supply chain riskDynamic require can indicate the package is performing dangerous or unsafe dynamic code execution.
Found 1 instance in 1 package
Minified code
QualityThis package contains minified code. This may be harmless in some cases where minified code is included in packaged libraries, however packages on npm should not minify code.
Found 1 instance in 1 package
155951
1371
0
215
0
28
1