mirror of
https://github.com/angular/angular.git
synced 2026-09-14 13:54:52 +08:00
refactor(core): Remove custom event and replay behavior. (#55695)
These behaviors have been moved back to g3. PR Close #55695
This commit is contained in:
committed by
Andrew Scott
parent
7c9d37d798
commit
eb31f2c775
@@ -32,8 +32,6 @@ export class EventContract implements UnrenamedEventContract {
|
||||
addEvent(eventType: string, prefixedEventType?: string): void;
|
||||
cleanUp(): void;
|
||||
// (undocumented)
|
||||
static CUSTOM_EVENT_SUPPORT: boolean;
|
||||
// (undocumented)
|
||||
ecaacs?: (updateEventInfoForA11yClick: typeof a11yClickLib.updateEventInfoForA11yClick, preventDefaultForA11yClick: typeof a11yClickLib.preventDefaultForA11yClick, populateClickOnlyAction: typeof a11yClickLib.populateClickOnlyAction) => void;
|
||||
ecrd(dispatcher: Dispatcher, restriction: Restriction): void;
|
||||
exportAddA11yClickSupport(): void;
|
||||
|
||||
@@ -32,7 +32,6 @@ const DEFAULT_EVENT_TYPE: string = EventType.CLICK;
|
||||
/** Resolves actions for Events. */
|
||||
export class ActionResolver {
|
||||
private a11yClickSupport: boolean = false;
|
||||
private readonly customEventSupport: boolean;
|
||||
private readonly syntheticMouseEventSupport: boolean;
|
||||
|
||||
private updateEventInfoForA11yClick?: (eventInfo: eventInfoLib.EventInfo) => void = undefined;
|
||||
@@ -46,30 +45,14 @@ export class ActionResolver {
|
||||
) => void = undefined;
|
||||
|
||||
constructor({
|
||||
customEventSupport = false,
|
||||
syntheticMouseEventSupport = false,
|
||||
}: {
|
||||
customEventSupport?: boolean;
|
||||
syntheticMouseEventSupport?: boolean;
|
||||
} = {}) {
|
||||
this.customEventSupport = customEventSupport;
|
||||
this.syntheticMouseEventSupport = syntheticMouseEventSupport;
|
||||
}
|
||||
|
||||
resolve(eventInfo: eventInfoLib.EventInfo) {
|
||||
if (this.customEventSupport) {
|
||||
if (eventInfoLib.getEventType(eventInfo) === EventType.CUSTOM) {
|
||||
const detail = (eventInfoLib.getEvent(eventInfo) as CustomEvent).detail;
|
||||
// For custom events, use a secondary dispatch based on the internal
|
||||
// custom type of the event.
|
||||
if (!detail || !detail['_type']) {
|
||||
// This should never happen.
|
||||
return;
|
||||
}
|
||||
eventInfoLib.setEventType(eventInfo, detail['_type']);
|
||||
}
|
||||
}
|
||||
|
||||
this.populateAction(eventInfo);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,124 +0,0 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright Google LLC All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by an MIT-style license that can be
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {EventType} from './/event_type';
|
||||
|
||||
// We use '_type' for the event contract, which lives in a separate
|
||||
// compilation unit.
|
||||
declare interface UnrenamedCustomEventDetail {
|
||||
_type: string;
|
||||
}
|
||||
|
||||
/** The detail interface provided for custom events. */
|
||||
export interface CustomEventDetail<T> {
|
||||
type: string;
|
||||
data?: T;
|
||||
triggeringEvent?: Event;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a custom event with the specified data.
|
||||
* @param type The type of the action, e.g., 'submit'.
|
||||
* @param data An optional data payload.
|
||||
* @param triggeringEvent The event that triggers this custom event. This can be
|
||||
* accessed from the custom event's action flow like so:
|
||||
* actionFlow.event().detail.triggeringEvent.
|
||||
* @return The new custom event.
|
||||
*/
|
||||
export function createCustomEvent<T>(type: string, data?: T, triggeringEvent?: Event): Event {
|
||||
let event: CustomEvent<CustomEventDetail<T> & UnrenamedCustomEventDetail>;
|
||||
const unrenamedDetail: UnrenamedCustomEventDetail = {
|
||||
'_type': type,
|
||||
};
|
||||
const renamedDetail: CustomEventDetail<T> = {
|
||||
type,
|
||||
data,
|
||||
triggeringEvent,
|
||||
};
|
||||
const detail = {...unrenamedDetail, ...renamedDetail};
|
||||
try {
|
||||
// We don't use the CustomEvent constructor directly since it isn't
|
||||
// supported in IE 9 or 10 and initCustomEvent below works just fine.
|
||||
event = document.createEvent('CustomEvent');
|
||||
event.initCustomEvent(EventType.CUSTOM, true, false, detail);
|
||||
} catch (e) {
|
||||
// If custom events aren't supported, fall back to custom-named HTMLEvent.
|
||||
// Fallback used by Android Gingerbread, FF4-5.
|
||||
|
||||
// Hack to emulate `CustomEvent`, `HTMLEvents` doesn't satisfy `CustomEvent`
|
||||
// type.
|
||||
// tslint:disable-next-line:no-any
|
||||
event = document.createEvent('HTMLEvents') as any;
|
||||
event.initEvent(EventType.CUSTOM, true, false);
|
||||
// Hack to emulate `CustomEvent`, `detail` is readonly on `CustomEvent`.
|
||||
// tslint:disable-next-line:no-any
|
||||
(event as any)['detail'] = detail;
|
||||
}
|
||||
|
||||
return event;
|
||||
}
|
||||
|
||||
/**
|
||||
* Fires a custom event with an optional payload. Only intended to be consumed
|
||||
* by jsaction itself. Supported in Firefox 6+, IE 9+, and all Chrome versions.
|
||||
*
|
||||
* `bootstrapCustomEventSupport` is required to add a listener that handles the
|
||||
* event.
|
||||
*
|
||||
* @param target The target element.
|
||||
* @param type The type of the action, e.g., 'submit'.
|
||||
* @param data An optional data payload.
|
||||
* @param triggeringEvent An optional data for the Event triggered this custom
|
||||
* event.
|
||||
*/
|
||||
export function fireCustomEvent<T>(
|
||||
target: Element,
|
||||
type: string,
|
||||
data?: T,
|
||||
triggeringEvent?: Event,
|
||||
) {
|
||||
const event = createCustomEvent(type, data, triggeringEvent);
|
||||
target.dispatchEvent(event);
|
||||
}
|
||||
|
||||
/**
|
||||
* Bootstraps `CustomEvent` support on the container.
|
||||
*
|
||||
* This is required to handle events fired by `fireCustomEvent` in the `container`.
|
||||
*
|
||||
* @param container The JSAction container to add an event listener on.
|
||||
* @param trigger A function that can trigger JSAction Event dispatch. Generally this function
|
||||
* should delegate to an `EventContract` handler for the event type provided defined by
|
||||
* `event.detail['_type']`.
|
||||
* @returns A function that removes the event listener.
|
||||
*/
|
||||
export function bootstrapCustomEventSupport(container: Element, trigger: (event: Event) => void) {
|
||||
const customEventListener = (event: Event) => {
|
||||
let customEvent = event as CustomEvent;
|
||||
const detail = customEvent.detail;
|
||||
// For custom events, use a secondary dispatch based on the internal
|
||||
// custom type of the event.
|
||||
if (!detail || !detail['_type']) {
|
||||
// This should never happen.
|
||||
return;
|
||||
}
|
||||
// Mirrors code from Wiz's internal `trigger()` method.
|
||||
const syntheticCustomEvent = {
|
||||
'type': detail['_type'],
|
||||
'target': event.target,
|
||||
'bubbles': true,
|
||||
detail,
|
||||
};
|
||||
// tslint:disable-next-line:no-any
|
||||
trigger(syntheticCustomEvent as any);
|
||||
};
|
||||
container.addEventListener('_custom', customEventListener);
|
||||
return () => {
|
||||
container.removeEventListener('_custom', customEventListener);
|
||||
};
|
||||
}
|
||||
@@ -17,9 +17,3 @@ export const A11Y_CLICK_SUPPORT = false;
|
||||
* flag can be overridden in a build rule.
|
||||
*/
|
||||
export const MOUSE_SPECIAL_SUPPORT = false;
|
||||
|
||||
/**
|
||||
* @define Support for custom events, which are type EventType.CUSTOM. These are
|
||||
* native DOM events with an additional type field and an optional payload.
|
||||
*/
|
||||
export const CUSTOM_EVENT_SUPPORT = false;
|
||||
|
||||
@@ -35,11 +35,7 @@ import {ActionResolver} from './action_resolver';
|
||||
import {EarlyJsactionData, EarlyJsactionDataContainer} from './earlyeventcontract';
|
||||
import * as eventLib from './event';
|
||||
import {EventContractContainerManager} from './event_contract_container';
|
||||
import {
|
||||
A11Y_CLICK_SUPPORT,
|
||||
CUSTOM_EVENT_SUPPORT,
|
||||
MOUSE_SPECIAL_SUPPORT,
|
||||
} from './event_contract_defines';
|
||||
import {A11Y_CLICK_SUPPORT, MOUSE_SPECIAL_SUPPORT} from './event_contract_defines';
|
||||
import * as eventInfoLib from './event_info';
|
||||
import {EventType} from './event_type';
|
||||
import {Restriction} from './restriction';
|
||||
@@ -87,14 +83,12 @@ type EventHandler = (eventType: string, event: Event, container: Element) => voi
|
||||
* be delay loaded in a generic way.
|
||||
*/
|
||||
export class EventContract implements UnrenamedEventContract {
|
||||
static CUSTOM_EVENT_SUPPORT = CUSTOM_EVENT_SUPPORT;
|
||||
static A11Y_CLICK_SUPPORT = A11Y_CLICK_SUPPORT;
|
||||
static MOUSE_SPECIAL_SUPPORT = MOUSE_SPECIAL_SUPPORT;
|
||||
|
||||
private containerManager: EventContractContainerManager | null;
|
||||
|
||||
private readonly actionResolver = new ActionResolver({
|
||||
customEventSupport: EventContract.CUSTOM_EVENT_SUPPORT,
|
||||
syntheticMouseEventSupport: EventContract.MOUSE_SPECIAL_SUPPORT,
|
||||
});
|
||||
|
||||
@@ -135,9 +129,6 @@ export class EventContract implements UnrenamedEventContract {
|
||||
|
||||
constructor(containerManager: EventContractContainerManager) {
|
||||
this.containerManager = containerManager;
|
||||
if (EventContract.CUSTOM_EVENT_SUPPORT) {
|
||||
this.addEvent(EventType.CUSTOM);
|
||||
}
|
||||
if (EventContract.A11Y_CLICK_SUPPORT) {
|
||||
// Add a11y click support to the `EventContract`.
|
||||
this.addA11yClickSupport();
|
||||
|
||||
@@ -1,459 +0,0 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright Google LLC All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by an MIT-style license that can be
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
/**
|
||||
* @fileoverview Functions for replaying events by the jsaction
|
||||
* Dispatcher.
|
||||
* All ts-ignores in this file are due to APIs that are no longer in the browser.
|
||||
*/
|
||||
|
||||
import {createCustomEvent} from './/custom_events';
|
||||
import * as jsactionEvent from './/event';
|
||||
import {EventType} from './/event_type';
|
||||
|
||||
type Writeable<T> = {
|
||||
-readonly [P in keyof T]: T[P];
|
||||
};
|
||||
|
||||
/**
|
||||
* Replays an event.
|
||||
*/
|
||||
export function replayEvent(event: Event, targetElement: Element, eventType?: string) {
|
||||
triggerEvent(targetElement, createEvent(event, eventType));
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if a given event was triggered by the keyboard.
|
||||
* @param eventType The event type.
|
||||
* @return Whether it's a keyboard event.
|
||||
*/
|
||||
function isKeyboardEvent(eventType: string): boolean {
|
||||
return (
|
||||
eventType === EventType.KEYPRESS ||
|
||||
eventType === EventType.KEYDOWN ||
|
||||
eventType === EventType.KEYUP
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if a given event was triggered by the mouse.
|
||||
* @param eventType The event type.
|
||||
* @return Whether it's a mouse event.
|
||||
*/
|
||||
function isMouseEvent(eventType: string): boolean {
|
||||
// TODO: Verify if Drag events should be bound here.
|
||||
return (
|
||||
eventType === EventType.CLICK ||
|
||||
eventType === EventType.DBLCLICK ||
|
||||
eventType === EventType.MOUSEDOWN ||
|
||||
eventType === EventType.MOUSEOVER ||
|
||||
eventType === EventType.MOUSEOUT ||
|
||||
eventType === EventType.MOUSEMOVE
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if a given event is a general UI event.
|
||||
* @param eventType The event type.
|
||||
* @return Whether it's a focus event.
|
||||
*/
|
||||
function isUiEvent(eventType: string): boolean {
|
||||
// Almost nobody supports the W3C method of creating FocusEvents.
|
||||
// For now, we're going to use the UIEvent as a super-interface.
|
||||
return (
|
||||
eventType === EventType.FOCUS ||
|
||||
eventType === EventType.BLUR ||
|
||||
eventType === EventType.FOCUSIN ||
|
||||
eventType === EventType.FOCUSOUT ||
|
||||
eventType === EventType.SCROLL
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a whitespace-delineated list of modifier keys that should be
|
||||
* considered to be active on the event's key. See details at
|
||||
* https://developer.mozilla.org/en/DOM/KeyboardEvent.
|
||||
* @param alt Alt pressed.
|
||||
* @param ctrl Control pressed.
|
||||
* @param meta Command pressed (OSX only).
|
||||
* @param shift Shift pressed.
|
||||
* @return The constructed modifier keys string.
|
||||
*/
|
||||
function createKeyboardModifiersList(
|
||||
alt: boolean,
|
||||
ctrl: boolean,
|
||||
meta: boolean,
|
||||
shift: boolean,
|
||||
): string {
|
||||
const keys = [];
|
||||
if (alt) {
|
||||
keys.push('Alt');
|
||||
}
|
||||
if (ctrl) {
|
||||
keys.push('Control');
|
||||
}
|
||||
if (meta) {
|
||||
keys.push('Meta');
|
||||
}
|
||||
if (shift) {
|
||||
keys.push('Shift');
|
||||
}
|
||||
return keys.join(' ');
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a UI event object for replaying through the DOM.
|
||||
* @param original The event to create a new event from.
|
||||
* @param opt_eventType The type this event is being handled as by jsaction.
|
||||
* e.g. blur events are handled as focusout
|
||||
* @return The event object.
|
||||
*/
|
||||
export function createUiEvent(original: Event, opt_eventType?: string): Event {
|
||||
let event: Writeable<UIEvent> & {originalTimestamp?: DOMHighResTimeStamp};
|
||||
if (document.createEvent) {
|
||||
const originalUiEvent = original as UIEvent;
|
||||
// Event creation as per W3C event model specification. This codepath
|
||||
// is used by most non-IE browsers and also by IE 9 and later.
|
||||
event = document.createEvent('UIEvent');
|
||||
// On IE and Opera < 12, we must provide non-undefined values to
|
||||
// initEvent, otherwise it will fail.
|
||||
event.initUIEvent(
|
||||
opt_eventType || originalUiEvent.type,
|
||||
originalUiEvent.bubbles !== undefined ? originalUiEvent.bubbles : true,
|
||||
originalUiEvent.cancelable || false,
|
||||
originalUiEvent.view || window,
|
||||
(original as CustomEvent).detail || 0,
|
||||
);
|
||||
// detail
|
||||
} else {
|
||||
// Older versions of IE (up to version 8) do not support the
|
||||
// W3C event model. Use the IE specific function instead.
|
||||
// Suppressing errors for ts-migration.
|
||||
// TS2339: Property 'createEventObject' does not exist on type 'Document'.
|
||||
// @ts-ignore
|
||||
event = document.createEventObject();
|
||||
event.type = opt_eventType || original.type;
|
||||
event.bubbles = original.bubbles !== undefined ? original.bubbles : true;
|
||||
event.cancelable = original.cancelable || false;
|
||||
event.view = (original as Writeable<UIEvent>).view || window;
|
||||
event.detail = (original as CustomEvent).detail || 0;
|
||||
}
|
||||
// Some focus events also have a nullable relatedTarget value which isn't
|
||||
// directly supported in the initUIEvent() method.
|
||||
(event as Writeable<FocusEvent>).relatedTarget = (original as FocusEvent).relatedTarget || null;
|
||||
event.originalTimestamp = original.timeStamp;
|
||||
return event;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a keyboard event object for replaying through the DOM.
|
||||
* @param original The event to create a new event from.
|
||||
* @param opt_eventType The type this event is being handled as by jsaction.
|
||||
* E.g. a keypress is handled as click in some cases.
|
||||
* @return The event object.
|
||||
* @suppress {strictMissingProperties} Two definitions of initKeyboardEvent.
|
||||
*/
|
||||
export function createKeyboardEvent(original: Event, opt_eventType?: string): Event {
|
||||
let event;
|
||||
const keyboardEvent = original as KeyboardEvent;
|
||||
if (document.createEvent) {
|
||||
// Event creation as per W3C event model specification. This codepath
|
||||
// is used by most non-IE browsers and also by IE 9 and later.
|
||||
event = document.createEvent('KeyboardEvent');
|
||||
if (event.initKeyboardEvent) {
|
||||
if (jsactionEvent.isIe) {
|
||||
// IE9+
|
||||
// https://docs.microsoft.com/en-us/previous-versions/windows/internet-explorer/ie-developer/platform-apis/ff975945(v=vs.85)
|
||||
const modifiers = createKeyboardModifiersList(
|
||||
keyboardEvent.altKey,
|
||||
keyboardEvent.ctrlKey,
|
||||
keyboardEvent.metaKey,
|
||||
keyboardEvent.shiftKey,
|
||||
);
|
||||
event.initKeyboardEvent(
|
||||
opt_eventType || keyboardEvent.type,
|
||||
true,
|
||||
true,
|
||||
window,
|
||||
keyboardEvent.key,
|
||||
keyboardEvent.location,
|
||||
// Suppressing errors for ts-migration.
|
||||
// TS2345: Argument of type 'string' is not assignable to
|
||||
// parameter of type 'boolean | undefined'.
|
||||
// @ts-ignore
|
||||
modifiers,
|
||||
keyboardEvent.repeat,
|
||||
// @ts-ignore This doesn't exist
|
||||
keyboardEvent.locale,
|
||||
);
|
||||
} else {
|
||||
// W3C DOM Level 3 Events model.
|
||||
// https://www.w3.org/TR/uievents/#idl-interface-KeyboardEvent-initializers
|
||||
event.initKeyboardEvent(
|
||||
opt_eventType || original.type,
|
||||
true,
|
||||
true,
|
||||
window,
|
||||
keyboardEvent.key,
|
||||
keyboardEvent.location,
|
||||
keyboardEvent.ctrlKey,
|
||||
keyboardEvent.altKey,
|
||||
keyboardEvent.shiftKey,
|
||||
keyboardEvent.metaKey,
|
||||
);
|
||||
Object.defineProperty(event, 'repeat', {
|
||||
get: () => (original as KeyboardEvent).repeat,
|
||||
enumerable: true,
|
||||
});
|
||||
// Add missing 'locale' which is not part of the spec.
|
||||
// https://bugs.chromium.org/p/chromium/issues/detail?id=168971
|
||||
Object.defineProperty(event, 'locale', {
|
||||
// Suppressing errors for ts-migration.
|
||||
// TS2339: Property 'locale' does not exist on type 'Event'.
|
||||
// @ts-ignore
|
||||
get: () => original.locale,
|
||||
enumerable: true,
|
||||
});
|
||||
// Apple WebKit has a non-standard altGraphKey that is not implemented
|
||||
// here.
|
||||
// https://developer.apple.com/documentation/webkitjs/keyboardevent/1633753-initkeyboardevent
|
||||
}
|
||||
// Blink and Webkit had a bug that causes the `charCode`, `keyCode`, and
|
||||
// `which` properties to always be unset when synthesizing a keyboard
|
||||
// event. Details at: https://bugs.webkit.org/show_bug.cgi?id=16735. With
|
||||
// these properties being deprecated, the bug has evolved into affecting
|
||||
// the `key` property. We work around it by redefining the `key` and
|
||||
// deprecated properties; a simple assignment here would fail because the
|
||||
// native properties are readonly.
|
||||
if (jsactionEvent.isWebKit) {
|
||||
if (keyboardEvent.key && event.key === '') {
|
||||
Object.defineProperty(event, 'key', {
|
||||
get: () => keyboardEvent.key,
|
||||
enumerable: true,
|
||||
});
|
||||
}
|
||||
}
|
||||
// Re-implement the deprecated `charCode`, `keyCode` and `which` which
|
||||
// are also an issue on IE9+.
|
||||
if (jsactionEvent.isWebKit || jsactionEvent.isIe || jsactionEvent.isGecko) {
|
||||
Object.defineProperty(event, 'charCode', {
|
||||
get: () => (original as KeyboardEvent).charCode,
|
||||
enumerable: true,
|
||||
});
|
||||
const keyCodeGetter = () => (original as KeyboardEvent).keyCode;
|
||||
Object.defineProperty(event, 'keyCode', {
|
||||
get: keyCodeGetter,
|
||||
enumerable: true,
|
||||
});
|
||||
Object.defineProperty(event, 'which', {
|
||||
get: keyCodeGetter,
|
||||
enumerable: true,
|
||||
});
|
||||
}
|
||||
} else {
|
||||
// Gecko only supports an older/deprecated version from DOM Level 2. See
|
||||
// https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent/initKeyEvent
|
||||
// for details.
|
||||
// @ts-ignore ditto
|
||||
event.initKeyEvent(
|
||||
opt_eventType || original.type,
|
||||
true,
|
||||
true,
|
||||
window,
|
||||
// Suppressing errors for ts-migration.
|
||||
// TS2339: Property 'ctrlKey' does not exist on type 'Event'.
|
||||
// @ts-ignore
|
||||
original.ctrlKey,
|
||||
// Suppressing errors for ts-migration.
|
||||
// TS2339: Property 'altKey' does not exist on type 'Event'.
|
||||
// @ts-ignore
|
||||
original.altKey,
|
||||
// Suppressing errors for ts-migration.
|
||||
// TS2339: Property 'shiftKey' does not exist on type 'Event'.
|
||||
// @ts-ignore
|
||||
original.shiftKey,
|
||||
// Suppressing errors for ts-migration.
|
||||
// TS2339: Property 'metaKey' does not exist on type 'Event'.
|
||||
// @ts-ignore
|
||||
original.metaKey,
|
||||
// Suppressing errors for ts-migration.
|
||||
// TS2339: Property 'keyCode' does not exist on type 'Event'.
|
||||
// @ts-ignore
|
||||
original.keyCode,
|
||||
// Suppressing errors for ts-migration.
|
||||
// TS2339: Property 'charCode' does not exist on type 'Event'.
|
||||
// @ts-ignore
|
||||
original.charCode,
|
||||
);
|
||||
}
|
||||
} else {
|
||||
// Older versions of IE (up to version 8) do not support the
|
||||
// W3C event model. Use the IE specific function instead.
|
||||
// Suppressing errors for ts-migration.
|
||||
// @ts-ignore
|
||||
event = document.createEventObject();
|
||||
event.type = opt_eventType || original.type;
|
||||
const originalKeyboardEvent = original as KeyboardEvent;
|
||||
event.repeat = originalKeyboardEvent.repeat;
|
||||
event.ctrlKey = originalKeyboardEvent.ctrlKey;
|
||||
event.altKey = originalKeyboardEvent.altKey;
|
||||
event.shiftKey = originalKeyboardEvent.shiftKey;
|
||||
event.metaKey = originalKeyboardEvent.metaKey;
|
||||
event.key = originalKeyboardEvent.key;
|
||||
event.keyCode = originalKeyboardEvent.keyCode;
|
||||
event.charCode = originalKeyboardEvent.charCode;
|
||||
}
|
||||
event.originalTimestamp = original.timeStamp;
|
||||
return event;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a mouse event object for replaying through the DOM.
|
||||
* @param original The event to create a new event from.
|
||||
* @param opt_eventType The type this event is being handled as by jsaction.
|
||||
* E.g. a keypress is handled as click in some cases.
|
||||
* @return The event object.
|
||||
*/
|
||||
export function createMouseEvent(original: Event, opt_eventType?: string): MouseEvent {
|
||||
let event;
|
||||
const originalMouseEvent = original as MouseEvent;
|
||||
if (document.createEvent) {
|
||||
// Event creation as per W3C event model specification. This codepath
|
||||
// is used by most non-IE browsers and also by IE 9 and later.
|
||||
event = document.createEvent('MouseEvent');
|
||||
// On IE and Opera < 12, we must provide non-undefined values to
|
||||
// initMouseEvent, otherwise it will fail.
|
||||
event.initMouseEvent(
|
||||
opt_eventType || original.type,
|
||||
true, // canBubble
|
||||
true, // cancelable
|
||||
window,
|
||||
(original as CustomEvent).detail || 1,
|
||||
originalMouseEvent.screenX || 0,
|
||||
originalMouseEvent.screenY || 0,
|
||||
originalMouseEvent.clientX || 0,
|
||||
originalMouseEvent.clientY || 0,
|
||||
originalMouseEvent.ctrlKey || false,
|
||||
originalMouseEvent.altKey || false,
|
||||
originalMouseEvent.shiftKey || false,
|
||||
originalMouseEvent.metaKey || false,
|
||||
originalMouseEvent.button || 0,
|
||||
originalMouseEvent.relatedTarget || null,
|
||||
);
|
||||
} else {
|
||||
// Older versions of IE (up to version 8) do not support the
|
||||
// W3C event model. Use the IE specific function instead.
|
||||
// @ts-ignore
|
||||
event = document.createEventObject();
|
||||
event.type = opt_eventType || original.type;
|
||||
event.clientX = originalMouseEvent.clientX;
|
||||
event.clientY = originalMouseEvent.clientY;
|
||||
event.button = originalMouseEvent.button;
|
||||
event.detail = (original as CustomEvent).detail;
|
||||
event.ctrlKey = originalMouseEvent.ctrlKey;
|
||||
event.altKey = originalMouseEvent.altKey;
|
||||
event.shiftKey = originalMouseEvent.shiftKey;
|
||||
event.metaKey = originalMouseEvent.metaKey;
|
||||
}
|
||||
event.originalTimestamp = original.timeStamp;
|
||||
return event;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a generic event object for replaying through the DOM.
|
||||
* @param original The event to create a new event from.
|
||||
* @param opt_eventType The type this event is being handled as by jsaction.
|
||||
* E.g. a keypress is handled as click in some cases.
|
||||
* @return The event object.
|
||||
*/
|
||||
function createGenericEvent(original: Event, opt_eventType?: string): Event {
|
||||
let event;
|
||||
if (document.createEvent) {
|
||||
// Event creation as per W3C event model specification. This codepath
|
||||
// is used by most non-IE browsers and also by IE 9 and later.
|
||||
event = document.createEvent('Event');
|
||||
event.initEvent(opt_eventType || original.type, true, true);
|
||||
} else {
|
||||
// Older versions of IE (up to version 8) do not support the
|
||||
// W3C event model. Use the IE specific function instead.
|
||||
// Suppressing errors for ts-migration.
|
||||
// TS2339: Property 'createEventObject' does not exist on type 'Document'.
|
||||
// @ts-ignore
|
||||
event = document.createEventObject();
|
||||
event.type = opt_eventType || original.type;
|
||||
}
|
||||
event.originalTimestamp = original.timeStamp;
|
||||
return event;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an event object for replaying through the DOM.
|
||||
* NOTE: This function is visible just for testing. Please don't use
|
||||
* it outside JsAction internal testing.
|
||||
* TODO: Add support for FocusEvent and WheelEvent.
|
||||
* @param base The event to create a new event from.
|
||||
* @param opt_eventType The type this event is being handled as by jsaction.
|
||||
* E.g. a keypress is handled as click in some cases.
|
||||
* @return The event object.
|
||||
*/
|
||||
export function createEvent(base: unknown, opt_eventType?: string): Event {
|
||||
const original = base as Event;
|
||||
let event;
|
||||
let eventType;
|
||||
if (original.type === EventType.CUSTOM) {
|
||||
eventType = EventType.CUSTOM;
|
||||
} else {
|
||||
eventType = opt_eventType || original.type;
|
||||
}
|
||||
|
||||
if (isKeyboardEvent(eventType)) {
|
||||
event = createKeyboardEvent(original, opt_eventType);
|
||||
} else if (isMouseEvent(eventType)) {
|
||||
event = createMouseEvent(original, opt_eventType);
|
||||
} else if (isUiEvent(eventType)) {
|
||||
event = createUiEvent(original, opt_eventType);
|
||||
} else if (eventType === EventType.CUSTOM) {
|
||||
event = createCustomEvent(
|
||||
opt_eventType!,
|
||||
(original as CustomEvent)['detail']['data'],
|
||||
(original as CustomEvent)['detail']['triggeringEvent'],
|
||||
);
|
||||
(event as {originalTimestamp?: number | null} | null)!.originalTimestamp = original.timeStamp;
|
||||
} else {
|
||||
// This ensures we don't send an undefined event object to the replayer.
|
||||
event = createGenericEvent(original, opt_eventType);
|
||||
}
|
||||
return event;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends an event for replay to the DOM.
|
||||
* @param target The target for the event.
|
||||
* @param event The event object.
|
||||
* @return The return value of the event replay, i.e., whether preventDefault()
|
||||
* was called on it.
|
||||
*/
|
||||
export function triggerEvent(target: EventTarget, event: Event): boolean {
|
||||
if (target.dispatchEvent) {
|
||||
return target.dispatchEvent(event);
|
||||
} else {
|
||||
// Suppressing errors for ts-migration.
|
||||
// TS2339: Property 'fireEvent' does not exist on type 'Element'.
|
||||
// @ts-ignore
|
||||
return (target as Element).fireEvent('on' + event.type, event);
|
||||
}
|
||||
}
|
||||
|
||||
/** Do not use outiside of testing. */
|
||||
export const testing = {
|
||||
createKeyboardModifiersList,
|
||||
createGenericEvent,
|
||||
isKeyboardEvent,
|
||||
isMouseEvent,
|
||||
isUiEvent,
|
||||
};
|
||||
@@ -14,10 +14,9 @@ import {
|
||||
EventInfoWrapper,
|
||||
unsetAction,
|
||||
} from '../src/event_info';
|
||||
import {createEvent} from '../src/replay';
|
||||
|
||||
function createMockClickEvent() {
|
||||
return createEvent({type: 'click'} as Event);
|
||||
function createClickEvent() {
|
||||
return new MouseEvent('click', {bubbles: true, cancelable: true});
|
||||
}
|
||||
|
||||
function createTestActionInfo({
|
||||
@@ -29,7 +28,7 @@ function createTestActionInfo({
|
||||
|
||||
function createTestEventInfo({
|
||||
eventType = 'click',
|
||||
event = createMockClickEvent(),
|
||||
event = createClickEvent(),
|
||||
targetElement = document.createElement('div'),
|
||||
container = document.createElement('div'),
|
||||
timestamp = 0,
|
||||
@@ -74,7 +73,7 @@ describe('dispatcher test.ts', () => {
|
||||
}),
|
||||
);
|
||||
expect(eventInfo).not.toBeNull();
|
||||
expect(eventInfo.getEventType()).toBe(createMockClickEvent().type);
|
||||
expect(eventInfo.getEventType()).toBe('click');
|
||||
expect(eventInfo.getAction()!.element).toBe(actionElement);
|
||||
});
|
||||
|
||||
@@ -219,7 +218,7 @@ describe('dispatcher test.ts', () => {
|
||||
dispatcher.registerGlobalHandler('click', handler);
|
||||
|
||||
const eventInfo = createTestEventInfo({
|
||||
event: createEvent({type: 'mousedown'} as Event),
|
||||
event: new MouseEvent('mousedown', {bubbles: true, cancelable: true}),
|
||||
eventType: 'mousedown',
|
||||
});
|
||||
unsetAction(eventInfo);
|
||||
|
||||
@@ -7,7 +7,6 @@
|
||||
*/
|
||||
|
||||
import * as cache from '../src/cache';
|
||||
import {bootstrapCustomEventSupport, fireCustomEvent} from '../src/custom_events';
|
||||
import {stopPropagation} from '../src/dispatcher';
|
||||
import {
|
||||
EarlyEventContract,
|
||||
@@ -99,12 +98,6 @@ const domContent = `
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="custom-event-container">
|
||||
<div id="custom-event-action-element" jsaction="custom-event:handleCustomEvent">
|
||||
<div id="custom-event-target-element"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="anchor-click-container">
|
||||
<a id="anchor-click-action-element" href="javascript:void(0);" jsaction="handleClick">
|
||||
<span id="anchor-click-target-element"></span>
|
||||
@@ -338,7 +331,6 @@ describe('EventContract', () => {
|
||||
safeElement.setInnerHtml(document.body, testonlyHtml(domContent));
|
||||
EventContract.A11Y_CLICK_SUPPORT = false;
|
||||
EventContract.MOUSE_SPECIAL_SUPPORT = false;
|
||||
EventContract.CUSTOM_EVENT_SUPPORT = false;
|
||||
|
||||
// Normalize timestamp.
|
||||
spyOn(Date, 'now').and.returnValue(0);
|
||||
@@ -852,102 +844,6 @@ describe('EventContract', () => {
|
||||
expect(eventContract.handler(EventType.CLICK)).toBeUndefined();
|
||||
});
|
||||
|
||||
describe('custom event', () => {
|
||||
beforeEach(() => {
|
||||
EventContract.CUSTOM_EVENT_SUPPORT = true;
|
||||
});
|
||||
|
||||
it('dispatches event', () => {
|
||||
const container = getRequiredElementById('custom-event-container');
|
||||
const targetElement = getRequiredElementById('custom-event-target-element');
|
||||
const actionElement = getRequiredElementById('custom-event-action-element');
|
||||
|
||||
const dispatcher = jasmine.createSpy<Dispatcher>('dispatcher');
|
||||
createEventContract({
|
||||
eventContractContainerManager: new EventContractContainer(container),
|
||||
eventTypes: ['custom-event'],
|
||||
dispatcher,
|
||||
});
|
||||
|
||||
// createEvent/initEvent is used to support IE11
|
||||
// tslint:disable:deprecation
|
||||
const customEvent = document.createEvent('Event');
|
||||
customEvent.initEvent('custom-event', true, true);
|
||||
// tslint:enable:deprecation
|
||||
targetElement.dispatchEvent(customEvent);
|
||||
|
||||
expect(dispatcher).toHaveBeenCalledTimes(1);
|
||||
const eventInfoWrapper = getLastDispatchedEventInfoWrapper(dispatcher);
|
||||
expect(eventInfoWrapper.getEventType()).toBe('custom-event');
|
||||
expect(eventInfoWrapper.getEvent()).toBe(customEvent);
|
||||
expect(eventInfoWrapper.getTargetElement()).toBe(targetElement);
|
||||
expect(eventInfoWrapper.getAction()?.name).toBe('handleCustomEvent');
|
||||
expect(eventInfoWrapper.getAction()?.element).toBe(actionElement);
|
||||
});
|
||||
|
||||
it('dispatches event with data', () => {
|
||||
const container = getRequiredElementById('custom-event-container');
|
||||
const targetElement = getRequiredElementById('custom-event-target-element');
|
||||
const actionElement = getRequiredElementById('custom-event-action-element');
|
||||
|
||||
const dispatcher = jasmine.createSpy<Dispatcher>('dispatcher');
|
||||
createEventContract({
|
||||
eventContractContainerManager: new EventContractContainer(container),
|
||||
eventTypes: ['custom-event'],
|
||||
dispatcher,
|
||||
});
|
||||
|
||||
const data = {'test': 1};
|
||||
fireCustomEvent(targetElement, 'custom-event', data);
|
||||
|
||||
expect(dispatcher).toHaveBeenCalledTimes(1);
|
||||
const eventInfoWrapper = getLastDispatchedEventInfoWrapper(dispatcher);
|
||||
expect(eventInfoWrapper.getEventType()).toBe('custom-event');
|
||||
const customEvent = eventInfoWrapper.getEvent() as CustomEvent;
|
||||
expect(customEvent.type).toBe(EventType.CUSTOM);
|
||||
expect(customEvent.detail.data).toBe(data);
|
||||
expect(eventInfoWrapper.getTargetElement()).toBe(targetElement);
|
||||
expect(eventInfoWrapper.getAction()?.name).toBe('handleCustomEvent');
|
||||
expect(eventInfoWrapper.getAction()?.element).toBe(actionElement);
|
||||
});
|
||||
});
|
||||
|
||||
describe('bootstrap custom event', () => {
|
||||
it('dispatches with fireCustomEvent', () => {
|
||||
const container = getRequiredElementById('custom-event-container');
|
||||
const targetElement = getRequiredElementById('custom-event-target-element');
|
||||
const actionElement = getRequiredElementById('custom-event-action-element');
|
||||
|
||||
const dispatcher = jasmine.createSpy<Dispatcher>('dispatcher');
|
||||
const eventContract = createEventContract({
|
||||
eventContractContainerManager: new EventContractContainer(container),
|
||||
eventTypes: [],
|
||||
dispatcher,
|
||||
});
|
||||
bootstrapCustomEventSupport(container, (event) => {
|
||||
let handler = eventContract.handler(event.type);
|
||||
if (!handler) {
|
||||
eventContract.addEvent(event.type);
|
||||
handler = eventContract.handler(event.type);
|
||||
}
|
||||
handler!(event.type, event, container);
|
||||
});
|
||||
|
||||
const data = {'test': 1};
|
||||
fireCustomEvent(targetElement, 'custom-event', data);
|
||||
|
||||
expect(dispatcher).toHaveBeenCalledTimes(1);
|
||||
const eventInfoWrapper = getLastDispatchedEventInfoWrapper(dispatcher);
|
||||
expect(eventInfoWrapper.getEventType()).toBe('custom-event');
|
||||
const customEvent = eventInfoWrapper.getEvent() as CustomEvent;
|
||||
expect(customEvent.type).toBe('custom-event');
|
||||
expect(customEvent.detail.data).toBe(data);
|
||||
expect(eventInfoWrapper.getTargetElement()).toBe(targetElement);
|
||||
expect(eventInfoWrapper.getAction()?.name).toBe('handleCustomEvent');
|
||||
expect(eventInfoWrapper.getAction()?.element).toBe(actionElement);
|
||||
});
|
||||
});
|
||||
|
||||
it('prevents default for click on anchor child', () => {
|
||||
const container = getRequiredElementById('anchor-click-container');
|
||||
const actionElement = getRequiredElementById('anchor-click-action-element');
|
||||
|
||||
Reference in New Issue
Block a user