// Copyright 2018-2019 the Deno authors. All rights reserved. MIT license. import * as domTypes from "./dom_types"; import { getPrivateValue, requiredArguments } from "./util"; // WeakMaps are recommended for private attributes (see MDN link below) // https://developer.mozilla.org/en-US/docs/Archive/Add-ons/Add-on_SDK/Guides/Contributor_s_Guide/Private_Properties#Using_WeakMaps export const eventAttributes = new WeakMap(); export class EventInit implements domTypes.EventInit { bubbles = false; cancelable = false; composed = false; constructor({ bubbles = false, cancelable = false, composed = false } = {}) { this.bubbles = bubbles; this.cancelable = cancelable; this.composed = composed; } } export class Event implements domTypes.Event { // Each event has the following associated flags private _canceledFlag = false; private _dispatchedFlag = false; private _initializedFlag = false; private _inPassiveListenerFlag = false; private _stopImmediatePropagationFlag = false; private _stopPropagationFlag = false; // Property for objects on which listeners will be invoked private _path: domTypes.EventPath[] = []; constructor(type: string, eventInitDict: domTypes.EventInit = {}) { requiredArguments("Event", arguments.length, 1); type = String(type); this._initializedFlag = true; eventAttributes.set(this, { type, bubbles: eventInitDict.bubbles || false, cancelable: eventInitDict.cancelable || false, composed: eventInitDict.composed || false, currentTarget: null, eventPhase: domTypes.EventPhase.NONE, isTrusted: false, relatedTarget: null, target: null, timeStamp: Date.now() }); } get bubbles(): boolean { return getPrivateValue(this, eventAttributes, "bubbles"); } get cancelBubble(): boolean { return this._stopPropagationFlag; } set cancelBubble(value: boolean) { this._stopPropagationFlag = value; } get cancelBubbleImmediately(): boolean { return this._stopImmediatePropagationFlag; } set cancelBubbleImmediately(value: boolean) { this._stopImmediatePropagationFlag = value; } get cancelable(): boolean { return getPrivateValue(this, eventAttributes, "cancelable"); } get composed(): boolean { return getPrivateValue(this, eventAttributes, "composed"); } get currentTarget(): domTypes.EventTarget { return getPrivateValue(this, eventAttributes, "currentTarget"); } set currentTarget(value: domTypes.EventTarget) { eventAttributes.set(this, { type: this.type, bubbles: this.bubbles, cancelable: this.cancelable, composed: this.composed, currentTarget: value, eventPhase: this.eventPhase, isTrusted: this.isTrusted, relatedTarget: this.relatedTarget, target: this.target, timeStamp: this.timeStamp }); } get defaultPrevented(): boolean { return this._canceledFlag; } get dispatched(): boolean { return this._dispatchedFlag; } set dispatched(value: boolean) { this._dispatchedFlag = value; } get eventPhase(): number { return getPrivateValue(this, eventAttributes, "eventPhase"); } set eventPhase(value: number) { eventAttributes.set(this, { type: this.type, bubbles: this.bubbles, cancelable: this.cancelable, composed: this.composed, currentTarget: this.currentTarget, eventPhase: value, isTrusted: this.isTrusted, relatedTarget: this.relatedTarget, target: this.target, timeStamp: this.timeStamp }); } get initialized(): boolean { return this._initializedFlag; } set inPassiveListener(value: boolean) { this._inPassiveListenerFlag = value; } get isTrusted(): boolean { return getPrivateValue(this, eventAttributes, "isTrusted"); } set isTrusted(value: boolean) { eventAttributes.set(this, { type: this.type, bubbles: this.bubbles, cancelable: this.cancelable, composed: this.composed, currentTarget: this.currentTarget, eventPhase: this.eventPhase, isTrusted: value, relatedTarget: this.relatedTarget, target: this.target, timeStamp: this.timeStamp }); } get path(): domTypes.EventPath[] { return this._path; } set path(value: domTypes.EventPath[]) { this._path = value; } get relatedTarget(): domTypes.EventTarget { return getPrivateValue(this, eventAttributes, "relatedTarget"); } set relatedTarget(value: domTypes.EventTarget) { eventAttributes.set(this, { type: this.type, bubbles: this.bubbles, cancelable: this.cancelable, composed: this.composed, currentTarget: this.currentTarget, eventPhase: this.eventPhase, isTrusted: this.isTrusted, relatedTarget: value, target: this.target, timeStamp: this.timeStamp }); } get target(): domTypes.EventTarget { return getPrivateValue(this, eventAttributes, "target"); } set target(value: domTypes.EventTarget) { eventAttributes.set(this, { type: this.type, bubbles: this.bubbles, cancelable: this.cancelable, composed: this.composed, currentTarget: this.currentTarget, eventPhase: this.eventPhase, isTrusted: this.isTrusted, relatedTarget: this.relatedTarget, target: value, timeStamp: this.timeStamp }); } get timeStamp(): Date { return getPrivateValue(this, eventAttributes, "timeStamp"); } get type(): string { return getPrivateValue(this, eventAttributes, "type"); } /** Returns the event’s path (objects on which listeners will be * invoked). This does not include nodes in shadow trees if the * shadow root was created with its ShadowRoot.mode closed. * * event.composedPath(); */ composedPath(): domTypes.EventPath[] { if (this._path.length === 0) { return []; } const composedPath: domTypes.EventPath[] = [ { item: this.currentTarget, itemInShadowTree: false, relatedTarget: null, rootOfClosedTree: false, slotInClosedTree: false, target: null, touchTargetList: [] } ]; let currentTargetIndex = 0; let currentTargetHiddenSubtreeLevel = 0; for (let index = this._path.length - 1; index >= 0; index--) { const { item, rootOfClosedTree, slotInClosedTree } = this._path[index]; if (rootOfClosedTree) { currentTargetHiddenSubtreeLevel++; } if (item === this.currentTarget) { currentTargetIndex = index; break; } if (slotInClosedTree) { currentTargetHiddenSubtreeLevel--; } } let currentHiddenLevel = currentTargetHiddenSubtreeLevel; let maxHiddenLevel = currentTargetHiddenSubtreeLevel; for (let i = currentTargetIndex - 1; i >= 0; i--) { const { item, rootOfClosedTree, slotInClosedTree } = this._path[i]; if (rootOfClosedTree) { currentHiddenLevel++; } if (currentHiddenLevel <= maxHiddenLevel) { composedPath.unshift({ item, itemInShadowTree: false, relatedTarget: null, rootOfClosedTree: false, slotInClosedTree: false, target: null, touchTargetList: [] }); } if (slotInClosedTree) { currentHiddenLevel--; if (currentHiddenLevel < maxHiddenLevel) { maxHiddenLevel = currentHiddenLevel; } } } currentHiddenLevel = currentTargetHiddenSubtreeLevel; maxHiddenLevel = currentTargetHiddenSubtreeLevel; for ( let index = currentTargetIndex + 1; index < this._path.length; index++ ) { const { item, rootOfClosedTree, slotInClosedTree } = this._path[index]; if (slotInClosedTree) { currentHiddenLevel++; } if (currentHiddenLevel <= maxHiddenLevel) { composedPath.push({ item, itemInShadowTree: false, relatedTarget: null, rootOfClosedTree: false, slotInClosedTree: false, target: null, touchTargetList: [] }); } if (rootOfClosedTree) { currentHiddenLevel--; if (currentHiddenLevel < maxHiddenLevel) { maxHiddenLevel = currentHiddenLevel; } } } return composedPath; } /** Cancels the event (if it is cancelable). * See https://dom.spec.whatwg.org/#set-the-canceled-flag * * event.preventDefault(); */ preventDefault(): void { if (this.cancelable && !this._inPassiveListenerFlag) { this._canceledFlag = true; } } /** Stops the propagation of events further along in the DOM. * * event.stopPropagation(); */ stopPropagation(): void { this._stopPropagationFlag = true; } /** For this particular event, no other listener will be called. * Neither those attached on the same element, nor those attached * on elements which will be traversed later (in capture phase, * for instance). * * event.stopImmediatePropagation(); */ stopImmediatePropagation(): void { this._stopPropagationFlag = true; this._stopImmediatePropagationFlag = true; } } /** Built-in objects providing `get` methods for our * interceptable JavaScript operations. */ Reflect.defineProperty(Event.prototype, "bubbles", { enumerable: true }); Reflect.defineProperty(Event.prototype, "cancelable", { enumerable: true }); Reflect.defineProperty(Event.prototype, "composed", { enumerable: true }); Reflect.defineProperty(Event.prototype, "currentTarget", { enumerable: true }); Reflect.defineProperty(Event.prototype, "defaultPrevented", { enumerable: true }); Reflect.defineProperty(Event.prototype, "dispatched", { enumerable: true }); Reflect.defineProperty(Event.prototype, "eventPhase", { enumerable: true }); Reflect.defineProperty(Event.prototype, "isTrusted", { enumerable: true }); Reflect.defineProperty(Event.prototype, "target", { enumerable: true }); Reflect.defineProperty(Event.prototype, "timeStamp", { enumerable: true }); Reflect.defineProperty(Event.prototype, "type", { enumerable: true });