deno/cli/js/event.ts

349 lines
9.5 KiB
TypeScript
Raw Normal View History

2020-01-02 20:13:47 +00:00
// Copyright 2018-2020 the Deno authors. All rights reserved. MIT license.
import * as domTypes from "./dom_types.ts";
import { getPrivateValue, requiredArguments } from "./util.ts";
2019-01-05 15:02:44 +00:00
// 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");
}
2019-01-05 15:02:44 +00:00
export class Event implements domTypes.Event {
// The default value is `false`.
// Use `defineProperty` to define on each instance, NOT on the prototype.
isTrusted!: boolean;
2019-01-05 15:02:44 +00:00
// Each event has the following associated flags
private _canceledFlag = false;
2019-05-27 13:20:34 +00:00
private _dispatchedFlag = false;
2019-01-23 12:20:53 +00:00
private _initializedFlag = false;
2019-01-05 15:02:44 +00:00
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);
2019-01-23 12:20:53 +00:00
this._initializedFlag = true;
2019-01-05 15:02:44 +00:00
eventAttributes.set(this, {
type,
bubbles: eventInitDict.bubbles || false,
cancelable: eventInitDict.cancelable || false,
composed: eventInitDict.composed || false,
currentTarget: null,
eventPhase: domTypes.EventPhase.NONE,
isTrusted: false,
2019-05-27 13:20:34 +00:00
relatedTarget: null,
2019-01-05 15:02:44 +00:00
target: null,
timeStamp: Date.now()
});
Reflect.defineProperty(this, "isTrusted", {
enumerable: true,
get: isTrusted
});
2019-01-05 15:02:44 +00:00
}
get bubbles(): boolean {
return getPrivateValue(this, eventAttributes, "bubbles");
}
get cancelBubble(): boolean {
return this._stopPropagationFlag;
}
2019-05-27 13:20:34 +00:00
set cancelBubble(value: boolean) {
this._stopPropagationFlag = value;
}
2019-01-05 15:02:44 +00:00
get cancelBubbleImmediately(): boolean {
return this._stopImmediatePropagationFlag;
}
2019-05-27 13:20:34 +00:00
set cancelBubbleImmediately(value: boolean) {
this._stopImmediatePropagationFlag = value;
}
2019-01-05 15:02:44 +00:00
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");
}
2019-05-27 13:20:34 +00:00
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
});
}
2019-01-05 15:02:44 +00:00
get defaultPrevented(): boolean {
return this._canceledFlag;
}
2019-01-23 12:20:53 +00:00
get dispatched(): boolean {
2019-05-27 13:20:34 +00:00
return this._dispatchedFlag;
}
set dispatched(value: boolean) {
this._dispatchedFlag = value;
2019-01-23 12:20:53 +00:00
}
2019-01-05 15:02:44 +00:00
get eventPhase(): number {
return getPrivateValue(this, eventAttributes, "eventPhase");
}
2019-05-27 13:20:34 +00:00
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
});
}
2019-01-23 12:20:53 +00:00
get initialized(): boolean {
return this._initializedFlag;
}
2019-05-27 13:20:34 +00:00
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
});
}
2019-01-05 15:02:44 +00:00
get target(): domTypes.EventTarget {
return getPrivateValue(this, eventAttributes, "target");
}
2019-05-27 13:20:34 +00:00
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
});
}
2019-01-05 15:02:44 +00:00
get timeStamp(): Date {
return getPrivateValue(this, eventAttributes, "timeStamp");
}
get type(): string {
return getPrivateValue(this, eventAttributes, "type");
}
/** Returns the events 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
});
2019-05-27 13:20:34 +00:00
Reflect.defineProperty(Event.prototype, "dispatched", { enumerable: true });
2019-01-05 15:02:44 +00:00
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 });