1
0
Fork 0
mirror of https://github.com/denoland/deno.git synced 2024-11-02 09:34:19 -04:00
denoland-deno/cli/js/web/event.ts

407 lines
9.2 KiB
TypeScript
Raw Normal View History

2020-01-02 15:13:47 -05:00
// Copyright 2018-2020 the Deno authors. All rights reserved. MIT license.
2019-01-05 10:02:44 -05:00
import * as domTypes from "./dom_types.d.ts";
import { defineEnumerableProps, requiredArguments } from "./util.ts";
import { assert } from "../util.ts";
/** Stores a non-accessible view of the event path which is used internally in
* the logic for determining the path of an event. */
export interface EventPath {
item: EventTarget;
itemInShadowTree: boolean;
relatedTarget: EventTarget | null;
rootOfClosedTree: boolean;
slotInClosedTree: boolean;
target: EventTarget | null;
touchTargetList: EventTarget[];
}
interface EventAttributes {
type: string;
bubbles: boolean;
cancelable: boolean;
composed: boolean;
currentTarget: EventTarget | null;
eventPhase: number;
target: EventTarget | null;
timeStamp: number;
}
interface EventData {
dispatched: boolean;
inPassiveListener: boolean;
isTrusted: boolean;
path: EventPath[];
stopImmediatePropagation: boolean;
}
const eventData = new WeakMap<Event, EventData>();
// accessors for non runtime visible data
export function getDispatched(event: Event): boolean {
return Boolean(eventData.get(event)?.dispatched);
}
export function getPath(event: Event): EventPath[] {
return eventData.get(event)?.path ?? [];
}
export function getStopImmediatePropagation(event: Event): boolean {
return Boolean(eventData.get(event)?.stopImmediatePropagation);
}
export function setCurrentTarget(
event: Event,
value: EventTarget | null
): void {
(event as EventImpl).currentTarget = value;
}
export function setDispatched(event: Event, value: boolean): void {
const data = eventData.get(event as Event);
if (data) {
data.dispatched = value;
}
}
export function setEventPhase(event: Event, value: number): void {
(event as EventImpl).eventPhase = value;
}
export function setInPassiveListener(event: Event, value: boolean): void {
const data = eventData.get(event as Event);
if (data) {
data.inPassiveListener = value;
}
}
export function setPath(event: Event, value: EventPath[]): void {
const data = eventData.get(event as Event);
if (data) {
data.path = value;
}
}
export function setRelatedTarget<T extends Event>(
event: T,
value: EventTarget | null
): void {
if ("relatedTarget" in event) {
(event as T & {
relatedTarget: EventTarget | null;
}).relatedTarget = value;
}
}
export function setTarget(event: Event, value: EventTarget | null): void {
(event as EventImpl).target = value;
}
export function setStopImmediatePropagation(
event: Event,
value: boolean
): void {
const data = eventData.get(event as Event);
if (data) {
data.stopImmediatePropagation = value;
}
}
// Type guards that widen the event type
export function hasRelatedTarget(
event: Event
): event is domTypes.FocusEvent | domTypes.MouseEvent {
return "relatedTarget" in event;
}
2019-01-05 10:02:44 -05:00
function isTrusted(this: Event): boolean {
return eventData.get(this)!.isTrusted;
}
export class EventImpl implements Event {
// The default value is `false`.
// Use `defineProperty` to define on each instance, NOT on the prototype.
isTrusted!: boolean;
#canceledFlag = false;
#stopPropagationFlag = false;
#attributes: EventAttributes;
constructor(type: string, eventInitDict: EventInit = {}) {
requiredArguments("Event", arguments.length, 1);
type = String(type);
this.#attributes = {
2019-01-05 10:02:44 -05:00
type,
bubbles: eventInitDict.bubbles ?? false,
cancelable: eventInitDict.cancelable ?? false,
composed: eventInitDict.composed ?? false,
2019-01-05 10:02:44 -05:00
currentTarget: null,
eventPhase: Event.NONE,
2019-01-05 10:02:44 -05:00
target: null,
timeStamp: Date.now(),
};
eventData.set(this, {
dispatched: false,
inPassiveListener: false,
isTrusted: false,
path: [],
stopImmediatePropagation: false,
2019-01-05 10:02:44 -05:00
});
Reflect.defineProperty(this, "isTrusted", {
enumerable: true,
get: isTrusted,
});
2019-01-05 10:02:44 -05:00
}
get bubbles(): boolean {
return this.#attributes.bubbles;
2019-01-05 10:02:44 -05:00
}
get cancelBubble(): boolean {
return this.#stopPropagationFlag;
2019-01-05 10:02:44 -05:00
}
2019-05-27 09:20:34 -04:00
set cancelBubble(value: boolean) {
this.#stopPropagationFlag = value;
2019-05-27 09:20:34 -04:00
}
2019-01-05 10:02:44 -05:00
get cancelable(): boolean {
return this.#attributes.cancelable;
2019-01-05 10:02:44 -05:00
}
get composed(): boolean {
return this.#attributes.composed;
2019-01-05 10:02:44 -05:00
}
get currentTarget(): EventTarget | null {
return this.#attributes.currentTarget;
2019-01-05 10:02:44 -05:00
}
set currentTarget(value: EventTarget | null) {
this.#attributes = {
2019-05-27 09:20:34 -04:00
type: this.type,
bubbles: this.bubbles,
cancelable: this.cancelable,
composed: this.composed,
currentTarget: value,
eventPhase: this.eventPhase,
target: this.target,
timeStamp: this.timeStamp,
};
2019-05-27 09:20:34 -04:00
}
2019-01-05 10:02:44 -05:00
get defaultPrevented(): boolean {
return this.#canceledFlag;
2019-01-23 07:20:53 -05:00
}
2019-01-05 10:02:44 -05:00
get eventPhase(): number {
return this.#attributes.eventPhase;
2019-01-05 10:02:44 -05:00
}
2019-05-27 09:20:34 -04:00
set eventPhase(value: number) {
this.#attributes = {
2019-05-27 09:20:34 -04:00
type: this.type,
bubbles: this.bubbles,
cancelable: this.cancelable,
composed: this.composed,
currentTarget: this.currentTarget,
eventPhase: value,
target: this.target,
timeStamp: this.timeStamp,
};
2019-05-27 09:20:34 -04:00
}
2019-01-23 07:20:53 -05:00
get initialized(): boolean {
return true;
2019-05-27 09:20:34 -04:00
}
get target(): EventTarget | null {
return this.#attributes.target;
2019-01-05 10:02:44 -05:00
}
set target(value: EventTarget | null) {
this.#attributes = {
2019-05-27 09:20:34 -04:00
type: this.type,
bubbles: this.bubbles,
cancelable: this.cancelable,
composed: this.composed,
currentTarget: this.currentTarget,
eventPhase: this.eventPhase,
target: value,
timeStamp: this.timeStamp,
};
2019-05-27 09:20:34 -04:00
}
get timeStamp(): number {
return this.#attributes.timeStamp;
2019-01-05 10:02:44 -05:00
}
get type(): string {
return this.#attributes.type;
2019-01-05 10:02:44 -05:00
}
composedPath(): EventTarget[] {
const path = eventData.get(this)!.path;
if (path.length === 0) {
2019-01-05 10:02:44 -05:00
return [];
}
assert(this.currentTarget);
const composedPath: EventPath[] = [
2019-01-05 10:02:44 -05:00
{
item: this.currentTarget,
itemInShadowTree: false,
relatedTarget: null,
rootOfClosedTree: false,
slotInClosedTree: false,
target: null,
touchTargetList: [],
},
2019-01-05 10:02:44 -05:00
];
let currentTargetIndex = 0;
let currentTargetHiddenSubtreeLevel = 0;
for (let index = path.length - 1; index >= 0; index--) {
const { item, rootOfClosedTree, slotInClosedTree } = path[index];
2019-01-05 10:02:44 -05:00
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 } = path[i];
2019-01-05 10:02:44 -05:00
if (rootOfClosedTree) {
currentHiddenLevel++;
}
if (currentHiddenLevel <= maxHiddenLevel) {
composedPath.unshift({
item,
itemInShadowTree: false,
relatedTarget: null,
rootOfClosedTree: false,
slotInClosedTree: false,
target: null,
touchTargetList: [],
2019-01-05 10:02:44 -05:00
});
}
if (slotInClosedTree) {
currentHiddenLevel--;
if (currentHiddenLevel < maxHiddenLevel) {
maxHiddenLevel = currentHiddenLevel;
}
}
}
currentHiddenLevel = currentTargetHiddenSubtreeLevel;
maxHiddenLevel = currentTargetHiddenSubtreeLevel;
for (let index = currentTargetIndex + 1; index < path.length; index++) {
const { item, rootOfClosedTree, slotInClosedTree } = path[index];
2019-01-05 10:02:44 -05:00
if (slotInClosedTree) {
currentHiddenLevel++;
}
if (currentHiddenLevel <= maxHiddenLevel) {
composedPath.push({
item,
itemInShadowTree: false,
relatedTarget: null,
rootOfClosedTree: false,
slotInClosedTree: false,
target: null,
touchTargetList: [],
2019-01-05 10:02:44 -05:00
});
}
if (rootOfClosedTree) {
currentHiddenLevel--;
if (currentHiddenLevel < maxHiddenLevel) {
maxHiddenLevel = currentHiddenLevel;
}
}
}
return composedPath.map((p) => p.item);
2019-01-05 10:02:44 -05:00
}
preventDefault(): void {
if (this.cancelable && !eventData.get(this)!.inPassiveListener) {
this.#canceledFlag = true;
2019-01-05 10:02:44 -05:00
}
}
stopPropagation(): void {
this.#stopPropagationFlag = true;
2019-01-05 10:02:44 -05:00
}
stopImmediatePropagation(): void {
this.#stopPropagationFlag = true;
eventData.get(this)!.stopImmediatePropagation = true;
}
get NONE(): number {
return Event.NONE;
}
get CAPTURING_PHASE(): number {
return Event.CAPTURING_PHASE;
}
get AT_TARGET(): number {
return Event.AT_TARGET;
}
get BUBBLING_PHASE(): number {
return Event.BUBBLING_PHASE;
}
static get NONE(): number {
return 0;
}
static get CAPTURING_PHASE(): number {
return 1;
}
static get AT_TARGET(): number {
return 2;
}
static get BUBBLING_PHASE(): number {
return 3;
2019-01-05 10:02:44 -05:00
}
}
defineEnumerableProps(EventImpl, [
"bubbles",
"cancelable",
"composed",
"currentTarget",
"defaultPrevented",
"eventPhase",
"target",
"timeStamp",
"type",
]);