diff options
Diffstat (limited to 'cli/js/event.ts')
-rw-r--r-- | cli/js/event.ts | 348 |
1 files changed, 348 insertions, 0 deletions
diff --git a/cli/js/event.ts b/cli/js/event.ts new file mode 100644 index 000000000..3efc1c517 --- /dev/null +++ b/cli/js/event.ts @@ -0,0 +1,348 @@ +// Copyright 2018-2019 the Deno authors. All rights reserved. MIT license. +import * as domTypes from "./dom_types.ts"; +import { getPrivateValue, requiredArguments } from "./util.ts"; + +// 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(); + +function isTrusted(this: Event): boolean { + return getPrivateValue(this, eventAttributes, "isTrusted"); +} + +export class Event implements domTypes.Event { + // The default value is `false`. + // Use `defineProperty` to define on each instance, NOT on the prototype. + isTrusted!: boolean; + // 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() + }); + Reflect.defineProperty(this, "isTrusted", { + enumerable: true, + get: isTrusted + }); + } + + 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 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, "target", { enumerable: true }); +Reflect.defineProperty(Event.prototype, "timeStamp", { enumerable: true }); +Reflect.defineProperty(Event.prototype, "type", { enumerable: true }); |