UNPKG

21.6 kBJavaScriptView Raw
1/**
2 * @author Toru Nagashima <https://github.com/mysticatea>
3 * @copyright 2015 Toru Nagashima. All rights reserved.
4 * See LICENSE file in root directory for full license.
5 */
6'use strict';
7
8Object.defineProperty(exports, '__esModule', { value: true });
9
10/**
11 * @typedef {object} PrivateData
12 * @property {EventTarget} eventTarget The event target.
13 * @property {{type:string}} event The original event object.
14 * @property {number} eventPhase The current event phase.
15 * @property {EventTarget|null} currentTarget The current event target.
16 * @property {boolean} canceled The flag to prevent default.
17 * @property {boolean} stopped The flag to stop propagation immediately.
18 * @property {Function|null} passiveListener The listener if the current listener is passive. Otherwise this is null.
19 * @property {number} timeStamp The unix time.
20 * @private
21 */
22
23/**
24 * Private data for event wrappers.
25 * @type {WeakMap<Event, PrivateData>}
26 * @private
27 */
28const privateData = new WeakMap();
29
30/**
31 * Cache for wrapper classes.
32 * @type {WeakMap<Object, Function>}
33 * @private
34 */
35const wrappers = new WeakMap();
36
37/**
38 * Get private data.
39 * @param {Event} event The event object to get private data.
40 * @returns {PrivateData} The private data of the event.
41 * @private
42 */
43function pd(event) {
44 const retv = privateData.get(event);
45 console.assert(retv != null, "'this' is expected an Event object, but got", event);
46 return retv
47}
48
49/**
50 * @see https://dom.spec.whatwg.org/#interface-event
51 * @private
52 */
53/**
54 * The event wrapper.
55 * @constructor
56 * @param {EventTarget} eventTarget The event target of this dispatching.
57 * @param {Event|{type:string}} event The original event to wrap.
58 */
59function Event(eventTarget, event) {
60 privateData.set(this, {
61 eventTarget,
62 event,
63 eventPhase: 2,
64 currentTarget: eventTarget,
65 canceled: false,
66 stopped: false,
67 passiveListener: null,
68 timeStamp: event.timeStamp || Date.now(),
69 });
70
71 // https://heycam.github.io/webidl/#Unforgeable
72 Object.defineProperty(this, "isTrusted", { value: false, enumerable: true });
73
74 // Define accessors
75 const keys = Object.keys(event);
76 for (let i = 0; i < keys.length; ++i) {
77 const key = keys[i];
78 if (!(key in this)) {
79 Object.defineProperty(this, key, defineRedirectDescriptor(key));
80 }
81 }
82}
83
84// Should be enumerable, but class methods are not enumerable.
85Event.prototype = {
86 /**
87 * The type of this event.
88 * @type {string}
89 */
90 get type() {
91 return pd(this).event.type
92 },
93
94 /**
95 * The target of this event.
96 * @type {EventTarget}
97 */
98 get target() {
99 return pd(this).eventTarget
100 },
101
102 /**
103 * The target of this event.
104 * @type {EventTarget}
105 */
106 get currentTarget() {
107 return pd(this).currentTarget
108 },
109
110 /**
111 * @returns {EventTarget[]} The composed path of this event.
112 */
113 composedPath() {
114 const currentTarget = pd(this).currentTarget;
115 if (currentTarget == null) {
116 return []
117 }
118 return [currentTarget]
119 },
120
121 /**
122 * Constant of NONE.
123 * @type {number}
124 */
125 get NONE() {
126 return 0
127 },
128
129 /**
130 * Constant of CAPTURING_PHASE.
131 * @type {number}
132 */
133 get CAPTURING_PHASE() {
134 return 1
135 },
136
137 /**
138 * Constant of AT_TARGET.
139 * @type {number}
140 */
141 get AT_TARGET() {
142 return 2
143 },
144
145 /**
146 * Constant of BUBBLING_PHASE.
147 * @type {number}
148 */
149 get BUBBLING_PHASE() {
150 return 3
151 },
152
153 /**
154 * The target of this event.
155 * @type {number}
156 */
157 get eventPhase() {
158 return pd(this).eventPhase
159 },
160
161 /**
162 * Stop event bubbling.
163 * @returns {void}
164 */
165 stopPropagation() {
166 const data = pd(this);
167 if (typeof data.event.stopPropagation === "function") {
168 data.event.stopPropagation();
169 }
170 },
171
172 /**
173 * Stop event bubbling.
174 * @returns {void}
175 */
176 stopImmediatePropagation() {
177 const data = pd(this);
178
179 data.stopped = true;
180 if (typeof data.event.stopImmediatePropagation === "function") {
181 data.event.stopImmediatePropagation();
182 }
183 },
184
185 /**
186 * The flag to be bubbling.
187 * @type {boolean}
188 */
189 get bubbles() {
190 return Boolean(pd(this).event.bubbles)
191 },
192
193 /**
194 * The flag to be cancelable.
195 * @type {boolean}
196 */
197 get cancelable() {
198 return Boolean(pd(this).event.cancelable)
199 },
200
201 /**
202 * Cancel this event.
203 * @returns {void}
204 */
205 preventDefault() {
206 const data = pd(this);
207 if (data.passiveListener != null) {
208 console.warn("Event#preventDefault() was called from a passive listener:", data.passiveListener);
209 return
210 }
211 if (!data.event.cancelable) {
212 return
213 }
214
215 data.canceled = true;
216 if (typeof data.event.preventDefault === "function") {
217 data.event.preventDefault();
218 }
219 },
220
221 /**
222 * The flag to indicate cancellation state.
223 * @type {boolean}
224 */
225 get defaultPrevented() {
226 return pd(this).canceled
227 },
228
229 /**
230 * The flag to be composed.
231 * @type {boolean}
232 */
233 get composed() {
234 return Boolean(pd(this).event.composed)
235 },
236
237 /**
238 * The unix time of this event.
239 * @type {number}
240 */
241 get timeStamp() {
242 return pd(this).timeStamp
243 },
244};
245
246// `constructor` is not enumerable.
247Object.defineProperty(Event.prototype, "constructor", { value: Event, configurable: true, writable: true });
248
249// Ensure `event instanceof window.Event` is `true`.
250if (typeof window !== "undefined" && typeof window.Event !== "undefined") {
251 Object.setPrototypeOf(Event.prototype, window.Event.prototype);
252
253 // Make association for wrappers.
254 wrappers.set(window.Event.prototype, Event);
255}
256
257/**
258 * Get the property descriptor to redirect a given property.
259 * @param {string} key Property name to define property descriptor.
260 * @returns {PropertyDescriptor} The property descriptor to redirect the property.
261 * @private
262 */
263function defineRedirectDescriptor(key) {
264 return {
265 get() {
266 return pd(this).event[key]
267 },
268 set(value) {
269 pd(this).event[key] = value;
270 },
271 configurable: true,
272 enumerable: true,
273 }
274}
275
276/**
277 * Get the property descriptor to call a given method property.
278 * @param {string} key Property name to define property descriptor.
279 * @returns {PropertyDescriptor} The property descriptor to call the method property.
280 * @private
281 */
282function defineCallDescriptor(key) {
283 return {
284 value() {
285 const event = pd(this).event;
286 return event[key].apply(event, arguments)
287 },
288 configurable: true,
289 enumerable: true,
290 }
291}
292
293/**
294 * Define new wrapper class.
295 * @param {Function} BaseEvent The base wrapper class.
296 * @param {Object} proto The prototype of the original event.
297 * @returns {Function} The defined wrapper class.
298 * @private
299 */
300function defineWrapper(BaseEvent, proto) {
301 const keys = Object.keys(proto);
302 if (keys.length === 0) {
303 return BaseEvent
304 }
305
306 /** CustomEvent */
307 function CustomEvent(eventTarget, event) {
308 BaseEvent.call(this, eventTarget, event);
309 }
310
311 CustomEvent.prototype = Object.create(BaseEvent.prototype, {
312 constructor: { value: CustomEvent, configurable: true, writable: true },
313 });
314
315 // Define accessors.
316 for (let i = 0; i < keys.length; ++i) {
317 const key = keys[i];
318 if (!(key in BaseEvent.prototype)) {
319 const descriptor = Object.getOwnPropertyDescriptor(proto, key);
320 const isFunc = (typeof descriptor.value === "function");
321 Object.defineProperty(
322 CustomEvent.prototype,
323 key,
324 isFunc ? defineCallDescriptor(key) : defineRedirectDescriptor(key)
325 );
326 }
327 }
328
329 return CustomEvent
330}
331
332/**
333 * Get the wrapper class of a given prototype.
334 * @param {Object} proto The prototype of the original event to get its wrapper.
335 * @returns {Function} The wrapper class.
336 * @private
337 */
338function getWrapper(proto) {
339 if (proto == null || proto === Object.prototype) {
340 return Event
341 }
342
343 let wrapper = wrappers.get(proto);
344 if (wrapper == null) {
345 wrapper = defineWrapper(getWrapper(Object.getPrototypeOf(proto)), proto);
346 wrappers.set(proto, wrapper);
347 }
348 return wrapper
349}
350
351/**
352 * Wrap a given event to management a dispatching.
353 * @param {EventTarget} eventTarget The event target of this dispatching.
354 * @param {Object} event The event to wrap.
355 * @returns {Event} The wrapper instance.
356 * @private
357 */
358function wrapEvent(eventTarget, event) {
359 const Wrapper = getWrapper(Object.getPrototypeOf(event));
360 return new Wrapper(eventTarget, event)
361}
362
363/**
364 * Get the stopped flag of a given event.
365 * @param {Event} event The event to get.
366 * @returns {boolean} The flag to stop propagation immediately.
367 * @private
368 */
369function isStopped(event) {
370 return pd(event).stopped
371}
372
373/**
374 * Set the current event phase of a given event.
375 * @param {Event} event The event to set current target.
376 * @param {number} eventPhase New event phase.
377 * @returns {void}
378 * @private
379 */
380function setEventPhase(event, eventPhase) {
381 pd(event).eventPhase = eventPhase;
382}
383
384/**
385 * Set the current target of a given event.
386 * @param {Event} event The event to set current target.
387 * @param {EventTarget|null} currentTarget New current target.
388 * @returns {void}
389 * @private
390 */
391function setCurrentTarget(event, currentTarget) {
392 pd(event).currentTarget = currentTarget;
393}
394
395/**
396 * Set a passive listener of a given event.
397 * @param {Event} event The event to set current target.
398 * @param {Function|null} passiveListener New passive listener.
399 * @returns {void}
400 * @private
401 */
402function setPassiveListener(event, passiveListener) {
403 pd(event).passiveListener = passiveListener;
404}
405
406/**
407 * @typedef {object} ListenerNode
408 * @property {Function} listener
409 * @property {1|2|3} listenerType
410 * @property {boolean} passive
411 * @property {boolean} once
412 * @property {ListenerNode|null} next
413 * @private
414 */
415
416/**
417 * @type {WeakMap<object, Map<string, ListenerNode>>}
418 * @private
419 */
420const listenersMap = new WeakMap();
421
422// Listener types
423const CAPTURE = 1;
424const BUBBLE = 2;
425const ATTRIBUTE = 3;
426
427/**
428 * Check whether a given value is an object or not.
429 * @param {any} x The value to check.
430 * @returns {boolean} `true` if the value is an object.
431 */
432function isObject(x) {
433 return x !== null && typeof x === "object" //eslint-disable-line no-restricted-syntax
434}
435
436/**
437 * Get listeners.
438 * @param {EventTarget} eventTarget The event target to get.
439 * @returns {Map<string, ListenerNode>} The listeners.
440 * @private
441 */
442function getListeners(eventTarget) {
443 const listeners = listenersMap.get(eventTarget);
444 console.assert(listeners != null, "'this' is expected an EventTarget object, but got", eventTarget);
445 return listeners || new Map()
446}
447
448/**
449 * Get the property descriptor for the event attribute of a given event.
450 * @param {string} eventName The event name to get property descriptor.
451 * @returns {PropertyDescriptor} The property descriptor.
452 * @private
453 */
454function defineEventAttributeDescriptor(eventName) {
455 return {
456 get() {
457 const listeners = getListeners(this);
458 let node = listeners.get(eventName);
459 while (node != null) {
460 if (node.listenerType === ATTRIBUTE) {
461 return node.listener
462 }
463 node = node.next;
464 }
465 return null
466 },
467
468 set(listener) {
469 if (typeof listener !== "function" && !isObject(listener)) {
470 listener = null; // eslint-disable-line no-param-reassign
471 }
472 const listeners = getListeners(this);
473
474 // Traverse to the tail while removing old value.
475 let prev = null;
476 let node = listeners.get(eventName);
477 while (node != null) {
478 if (node.listenerType === ATTRIBUTE) {
479 // Remove old value.
480 if (prev !== null) {
481 prev.next = node.next;
482 }
483 else if (node.next !== null) {
484 listeners.set(eventName, node.next);
485 }
486 else {
487 listeners.delete(eventName);
488 }
489 }
490 else {
491 prev = node;
492 }
493
494 node = node.next;
495 }
496
497 // Add new value.
498 if (listener !== null) {
499 const newNode = {
500 listener,
501 listenerType: ATTRIBUTE,
502 passive: false,
503 once: false,
504 next: null,
505 };
506 if (prev === null) {
507 listeners.set(eventName, newNode);
508 }
509 else {
510 prev.next = newNode;
511 }
512 }
513 },
514 configurable: true,
515 enumerable: true,
516 }
517}
518
519/**
520 * Define an event attribute (e.g. `eventTarget.onclick`).
521 * @param {Object} eventTargetPrototype The event target prototype to define an event attrbite.
522 * @param {string} eventName The event name to define.
523 * @returns {void}
524 */
525function defineEventAttribute(eventTargetPrototype, eventName) {
526 Object.defineProperty(eventTargetPrototype, `on${eventName}`, defineEventAttributeDescriptor(eventName));
527}
528
529/**
530 * Define a custom EventTarget with event attributes.
531 * @param {string[]} eventNames Event names for event attributes.
532 * @returns {EventTarget} The custom EventTarget.
533 * @private
534 */
535function defineCustomEventTarget(eventNames) {
536 /** CustomEventTarget */
537 function CustomEventTarget() {
538 EventTarget.call(this);
539 }
540
541 CustomEventTarget.prototype = Object.create(EventTarget.prototype, {
542 constructor: { value: CustomEventTarget, configurable: true, writable: true },
543 });
544
545 for (let i = 0; i < eventNames.length; ++i) {
546 defineEventAttribute(CustomEventTarget.prototype, eventNames[i]);
547 }
548
549 return CustomEventTarget
550}
551
552/**
553 * EventTarget.
554 *
555 * - This is constructor if no arguments.
556 * - This is a function which returns a CustomEventTarget constructor if there are arguments.
557 *
558 * For example:
559 *
560 * class A extends EventTarget {}
561 * class B extends EventTarget("message") {}
562 * class C extends EventTarget("message", "error") {}
563 * class D extends EventTarget(["message", "error"]) {}
564 */
565function EventTarget() {
566 /*eslint-disable consistent-return */
567 if (this instanceof EventTarget) {
568 listenersMap.set(this, new Map());
569 return
570 }
571 if (arguments.length === 1 && Array.isArray(arguments[0])) {
572 return defineCustomEventTarget(arguments[0])
573 }
574 if (arguments.length > 0) {
575 const types = new Array(arguments.length);
576 for (let i = 0; i < arguments.length; ++i) {
577 types[i] = arguments[i];
578 }
579 return defineCustomEventTarget(types)
580 }
581 throw new TypeError("Cannot call a class as a function")
582 /*eslint-enable consistent-return */
583}
584
585// Should be enumerable, but class methods are not enumerable.
586EventTarget.prototype = {
587 /**
588 * Add a given listener to this event target.
589 * @param {string} eventName The event name to add.
590 * @param {Function} listener The listener to add.
591 * @param {boolean|{capture?:boolean,passive?:boolean,once?:boolean}} [options] The options for this listener.
592 * @returns {boolean} `true` if the listener was added actually.
593 */
594 addEventListener(eventName, listener, options) {
595 if (listener == null) {
596 return false
597 }
598 if (typeof listener !== "function" && !isObject(listener)) {
599 throw new TypeError("'listener' should be a function or an object.")
600 }
601
602 const listeners = getListeners(this);
603 const optionsIsObj = isObject(options);
604 const capture = optionsIsObj ? Boolean(options.capture) : Boolean(options);
605 const listenerType = (capture ? CAPTURE : BUBBLE);
606 const newNode = {
607 listener,
608 listenerType,
609 passive: optionsIsObj && Boolean(options.passive),
610 once: optionsIsObj && Boolean(options.once),
611 next: null,
612 };
613
614 // Set it as the first node if the first node is null.
615 let node = listeners.get(eventName);
616 if (node === undefined) {
617 listeners.set(eventName, newNode);
618 return true
619 }
620
621 // Traverse to the tail while checking duplication..
622 let prev = null;
623 while (node != null) {
624 if (node.listener === listener && node.listenerType === listenerType) {
625 // Should ignore duplication.
626 return false
627 }
628 prev = node;
629 node = node.next;
630 }
631
632 // Add it.
633 prev.next = newNode;
634 return true
635 },
636
637 /**
638 * Remove a given listener from this event target.
639 * @param {string} eventName The event name to remove.
640 * @param {Function} listener The listener to remove.
641 * @param {boolean|{capture?:boolean,passive?:boolean,once?:boolean}} [options] The options for this listener.
642 * @returns {boolean} `true` if the listener was removed actually.
643 */
644 removeEventListener(eventName, listener, options) {
645 if (listener == null) {
646 return false
647 }
648
649 const listeners = getListeners(this);
650 const capture = isObject(options) ? Boolean(options.capture) : Boolean(options);
651 const listenerType = (capture ? CAPTURE : BUBBLE);
652
653 let prev = null;
654 let node = listeners.get(eventName);
655 while (node != null) {
656 if (node.listener === listener && node.listenerType === listenerType) {
657 if (prev !== null) {
658 prev.next = node.next;
659 }
660 else if (node.next !== null) {
661 listeners.set(eventName, node.next);
662 }
663 else {
664 listeners.delete(eventName);
665 }
666 return true
667 }
668
669 prev = node;
670 node = node.next;
671 }
672
673 return false
674 },
675
676 /**
677 * Dispatch a given event.
678 * @param {Event|{type:string}} event The event to dispatch.
679 * @returns {boolean} `false` if canceled.
680 */
681 dispatchEvent(event) {
682 if (event == null || typeof event.type !== "string") {
683 throw new TypeError("\"event.type\" should be a string.")
684 }
685
686 // If listeners aren't registered, terminate.
687 const listeners = getListeners(this);
688 const eventName = event.type;
689 let node = listeners.get(eventName);
690 if (node == null) {
691 return true
692 }
693
694 // Since we cannot rewrite several properties, so wrap object.
695 const wrappedEvent = wrapEvent(this, event);
696
697 // This doesn't process capturing phase and bubbling phase.
698 // This isn't participating in a tree.
699 let prev = null;
700 while (node != null) {
701 // Remove this listener if it's once
702 if (node.once) {
703 if (prev !== null) {
704 prev.next = node.next;
705 }
706 else if (node.next !== null) {
707 listeners.set(eventName, node.next);
708 }
709 else {
710 listeners.delete(eventName);
711 }
712 }
713 else {
714 prev = node;
715 }
716
717 // Call this listener
718 setPassiveListener(wrappedEvent, (node.passive ? node.listener : null));
719 if (typeof node.listener === "function") {
720 node.listener.call(this, wrappedEvent);
721 }
722 else if (node.listenerType !== ATTRIBUTE && typeof node.listener.handleEvent === "function") {
723 node.listener.handleEvent(wrappedEvent);
724 }
725
726 // Break if `event.stopImmediatePropagation` was called.
727 if (isStopped(wrappedEvent)) {
728 break
729 }
730
731 node = node.next;
732 }
733 setPassiveListener(wrappedEvent, null);
734 setEventPhase(wrappedEvent, 0);
735 setCurrentTarget(wrappedEvent, null);
736
737 return !wrappedEvent.defaultPrevented
738 },
739};
740
741// `constructor` is not enumerable.
742Object.defineProperty(EventTarget.prototype, "constructor", { value: EventTarget, configurable: true, writable: true });
743
744// Ensure `eventTarget instanceof window.EventTarget` is `true`.
745if (typeof window !== "undefined" && typeof window.EventTarget !== "undefined") {
746 Object.setPrototypeOf(EventTarget.prototype, window.EventTarget.prototype);
747}
748
749exports.defineEventAttribute = defineEventAttribute;
750exports.EventTarget = EventTarget;
751exports['default'] = EventTarget;
752
753module.exports = EventTarget
754module.exports.EventTarget = module.exports["default"] = EventTarget
755module.exports.defineEventAttribute = defineEventAttribute
756//# sourceMappingURL=event-target-shim.js.map