diff --git a/packages/core/data/dom-events/dom-event.ts b/packages/core/data/dom-events/dom-event.ts index baa958415a..9edbd293d4 100644 --- a/packages/core/data/dom-events/dom-event.ts +++ b/packages/core/data/dom-events/dom-event.ts @@ -10,7 +10,7 @@ const timeOrigin = Date.now(); */ const emptyArray = [] as const; -export class DOMEvent { +export class DOMEvent implements Event { /** * @private * Internal API to facilitate testing - to be removed once we've completed @@ -119,7 +119,7 @@ export class DOMEvent { // From CustomEvent rather than Event. Can consider factoring out this // aspect into DOMCustomEvent. - private readonly detail: unknown | null; + readonly detail: unknown | null; private propagationState: EventPropagationState = EventPropagationState.resume; diff --git a/packages/core/data/observable/index.ts b/packages/core/data/observable/index.ts index 9043f76a50..6579cd1b7e 100644 --- a/packages/core/data/observable/index.ts +++ b/packages/core/data/observable/index.ts @@ -1,8 +1,6 @@ import type { ViewBase } from '../../ui/core/view-base'; import { DOMEvent } from '../dom-events/dom-event'; -import { Observable as ObservableDefinition, WrappedValue as WrappedValueDefinition } from '.'; - /** * Base event data. */ @@ -51,7 +49,7 @@ let _wrappedIndex = 0; * By default property change will not be fired for a same object. * By wrapping object into a WrappedValue instance `same object restriction` will be passed. */ -export class WrappedValue implements WrappedValueDefinition { +export class WrappedValue { /** * Creates an instance of WrappedValue object. * @param wrapped - the real value which should be wrapped. @@ -96,7 +94,7 @@ const _globalEventHandlers: { * Please note that should you be using the `new Observable({})` constructor, it is **obsolete** since v3.0, * and you have to migrate to the "data/observable" `fromObject({})` or the `fromObjectRecursive({})` functions. */ -export class Observable implements ObservableDefinition { +export class Observable implements EventTarget { /** * String value used when hooking to propertyChange event. */ @@ -186,27 +184,27 @@ export class Observable implements ObservableDefinition { * @param thisArg An optional parameter which when set will be used as "this" in callback method call. * @param options An optional parameter. If passed as a boolean, configures the useCapture value. Otherwise, specifies options. */ - public addEventListener(eventNames: string, callback: (data: EventData) => void, thisArg?: any, options?: AddEventListenerOptions | boolean): void { + public addEventListener(eventNames: string, callback: EventListenerOrEventListenerObject | ((data: EventData) => void), thisArg?: any, options?: AddEventListenerOptions | boolean): void { if (typeof eventNames !== 'string') { throw new TypeError('Events name(s) must be string.'); } if (typeof callback !== 'function') { - throw new TypeError('callback must be function.'); + throw new TypeError('Callback must be function.'); } const events = eventNames.trim().split(eventDelimiterPattern); for (let i = 0, l = events.length; i < l; i++) { const event = events[i]; const list = this.getEventList(event, true); - if (Observable._indexOfListener(list, callback, thisArg, options) >= 0) { + if (Observable._indexOfListener(list, callback as (data: EventData) => void, thisArg, options) >= 0) { // Don't allow addition of duplicate event listeners. continue; } // TODO: Performance optimization - if we do not have the thisArg specified, do not wrap the callback in additional object (ObserveEntry) list.push({ - callback, + callback: callback as (data: EventData) => void, thisArg, ...normalizeEventOptions(options), }); @@ -220,7 +218,7 @@ export class Observable implements ObservableDefinition { * @param thisArg An optional parameter which when set will be used to refine search of the correct callback which will be removed as event listener. * @param options An optional parameter. If passed as a boolean, configures the useCapture value. Otherwise, specifies options. */ - public removeEventListener(eventNames: string, callback?: (data: EventData) => void, thisArg?: any, options?: EventListenerOptions | boolean): void { + public removeEventListener(eventNames: string, callback?: EventListenerOrEventListenerObject | ((data: EventData) => void), thisArg?: any, options?: EventListenerOptions | boolean): void { if (typeof eventNames !== 'string') { throw new TypeError('Events name(s) must be string.'); } @@ -236,14 +234,16 @@ export class Observable implements ObservableDefinition { } const list = this.getEventList(event, false); - if (list) { - const index = Observable._indexOfListener(list, callback, thisArg, options); - if (index >= 0) { - list.splice(index, 1); - } - if (list.length === 0) { - delete this._observers[event]; - } + if (!list) { + continue; + } + + const index = Observable._indexOfListener(list, callback as (data: EventData) => void, thisArg, options); + if (index >= 0) { + list.splice(index, 1); + } + if (list.length === 0) { + delete this._observers[event]; } } } @@ -260,13 +260,13 @@ export class Observable implements ObservableDefinition { this.removeEventListener(eventName, callback, thisArg, options); } - public static removeEventListener(eventName: string, callback?: (data: EventData) => void, thisArg?: any, options?: EventListenerOptions | boolean): void { + public static removeEventListener(eventName: string, callback?: EventListenerOrEventListenerObject | ((data: EventData) => void), thisArg?: any, options?: EventListenerOptions | boolean): void { if (typeof eventName !== 'string') { throw new TypeError('Event must be string.'); } if (callback && typeof callback !== 'function') { - throw new TypeError('callback must be function.'); + throw new TypeError('Callback, if provided, must be function.'); } const eventClass = this.name === 'Observable' ? '*' : this.name; @@ -278,7 +278,7 @@ export class Observable implements ObservableDefinition { const events = _globalEventHandlers[eventClass][eventName]; if (callback) { - const index = Observable._indexOfListener(events, callback, thisArg, options); + const index = Observable._indexOfListener(events, callback as (data: EventData) => void, thisArg, options); if (index >= 0) { events.splice(index, 1); } @@ -299,13 +299,13 @@ export class Observable implements ObservableDefinition { } } - public static addEventListener(eventName: string, callback: (data: EventData) => void, thisArg?: any, options?: AddEventListenerOptions | boolean): void { + public static addEventListener(eventName: string, callback: EventListenerOrEventListenerObject | ((data: EventData) => void), thisArg?: any, options?: AddEventListenerOptions | boolean): void { if (typeof eventName !== 'string') { throw new TypeError('Event must be string.'); } if (typeof callback !== 'function') { - throw new TypeError('callback must be function.'); + throw new TypeError('Callback must be function.'); } const eventClass = this.name === 'Observable' ? '*' : this.name; @@ -317,13 +317,13 @@ export class Observable implements ObservableDefinition { } const list = _globalEventHandlers[eventClass][eventName]; - if (Observable._indexOfListener(list, callback, thisArg, options) >= 0) { + if (Observable._indexOfListener(list, callback as (data: EventData) => void, thisArg, options) >= 0) { // Don't allow addition of duplicate event listeners. return; } _globalEventHandlers[eventClass][eventName].push({ - callback, + callback: callback as (data: EventData) => void, thisArg, ...normalizeEventOptions(options), }); @@ -384,6 +384,21 @@ export class Observable implements ObservableDefinition { }); } + dispatchEvent(event: DOMEvent): boolean { + const data = { + eventName: event.type, + object: this, + detail: event.detail, + }; + + return event.dispatchTo({ + target: this, + data, + getGlobalEventHandlersPreHandling: () => this._getGlobalEventHandlers(data, 'First'), + getGlobalEventHandlersPostHandling: () => this._getGlobalEventHandlers(data, ''), + }); + } + private _getGlobalEventHandlers(data: EventData, eventType: 'First' | ''): ListenerEntry[] { const eventClass = data.object?.constructor?.name; const globalEventHandlersForOwnClass = _globalEventHandlers[eventClass]?.[`${data.eventName}${eventType}`] ?? []; diff --git a/packages/core/ui/core/view/view-common.ts b/packages/core/ui/core/view/view-common.ts index e61da07738..b179d6434d 100644 --- a/packages/core/ui/core/view/view-common.ts +++ b/packages/core/ui/core/view/view-common.ts @@ -292,7 +292,11 @@ export abstract class ViewCommon extends ViewBase implements ViewDefinition { return this._gestureObservers[type] || []; } - public addEventListener(arg: string | GestureTypes, callback: (data: EventData) => void, thisArg?: any, options?: AddEventListenerOptions | boolean): void { + public addEventListener(arg: string | GestureTypes, callback: EventListenerOrEventListenerObject | ((data: EventData) => void), thisArg?: any, options?: AddEventListenerOptions | boolean): void { + if (typeof callback !== 'function') { + throw new TypeError('Callback must be function.'); + } + // To avoid a full refactor of the Gestures system when migrating to DOM // Events, we mirror the this._gestureObservers record, creating // corresponding DOM Event listeners for each gesture. @@ -308,7 +312,7 @@ export abstract class ViewCommon extends ViewBase implements ViewDefinition { // the same time). if (typeof arg === 'number') { - this._observe(arg, callback, thisArg, options); + this._observe(arg, callback as (data: EventData) => void, thisArg, options); super.addEventListener(gestureToString(arg), callback, thisArg, options); return; } @@ -320,15 +324,19 @@ export abstract class ViewCommon extends ViewBase implements ViewDefinition { for (const event of events) { const gesture = gestureFromString(event); if (gesture && !this._isEvent(arg)) { - this._observe(gesture, callback, thisArg, options); + this._observe(gesture, callback as (data: EventData) => void, thisArg, options); } super.addEventListener(event, callback, thisArg, options); } } - public removeEventListener(arg: string | GestureTypes, callback?: (data: EventData) => void, thisArg?: any, options?: EventListenerOptions | boolean): void { + public removeEventListener(arg: string | GestureTypes, callback?: EventListenerOrEventListenerObject | ((data: EventData) => void), thisArg?: any, options?: EventListenerOptions | boolean): void { + if (callback && typeof callback !== 'function') { + throw new TypeError('Callback, if provided, must be function.'); + } + if (typeof arg === 'number') { - this._disconnectGestureObservers(arg, callback, thisArg, options); + this._disconnectGestureObservers(arg, callback as (data: EventData) => void, thisArg, options); super.removeEventListener(gestureToString(arg), callback, thisArg, options); return; } @@ -338,7 +346,7 @@ export abstract class ViewCommon extends ViewBase implements ViewDefinition { for (const event of events) { const gesture = gestureFromString(event); if (gesture && !this._isEvent(arg)) { - this._disconnectGestureObservers(gesture, callback, thisArg, options); + this._disconnectGestureObservers(gesture, callback as (data: EventData) => void, thisArg, options); } super.removeEventListener(event, callback, thisArg, options); } diff --git a/packages/core/ui/scroll-view/scroll-view-common.ts b/packages/core/ui/scroll-view/scroll-view-common.ts index c2bb7fc670..06fe9fdbed 100644 --- a/packages/core/ui/scroll-view/scroll-view-common.ts +++ b/packages/core/ui/scroll-view/scroll-view-common.ts @@ -16,7 +16,7 @@ export abstract class ScrollViewBase extends ContentView implements ScrollViewDe public scrollBarIndicatorVisible: boolean; public isScrollEnabled: boolean; - public addEventListener(arg: string, callback: (data: EventData) => void, thisArg?: any, options?: AddEventListenerOptions | boolean): void { + public addEventListener(arg: string, callback: EventListenerOrEventListenerObject | ((data: EventData) => void), thisArg?: any, options?: AddEventListenerOptions | boolean): void { super.addEventListener(arg, callback, thisArg, options); if (arg === ScrollViewBase.scrollEvent) { @@ -25,7 +25,7 @@ export abstract class ScrollViewBase extends ContentView implements ScrollViewDe } } - public removeEventListener(arg: string, callback?: (data: EventData) => void, thisArg?: any, options?: EventListenerOptions | boolean): void { + public removeEventListener(arg: string, callback?: EventListenerOrEventListenerObject | ((data: EventData) => void), thisArg?: any, options?: EventListenerOptions | boolean): void { super.removeEventListener(arg, callback, thisArg, options); if (arg === ScrollViewBase.scrollEvent) { diff --git a/packages/core/ui/text-base/span.ts b/packages/core/ui/text-base/span.ts index 734519606d..a1bd14ee02 100644 --- a/packages/core/ui/text-base/span.ts +++ b/packages/core/ui/text-base/span.ts @@ -81,12 +81,12 @@ export class Span extends ViewBase implements SpanDefinition { return this._tappable; } - addEventListener(arg: string, callback: (data: EventData) => void, thisArg?: any, options?: AddEventListenerOptions | boolean): void { + addEventListener(arg: string, callback: EventListenerOrEventListenerObject | ((data: EventData) => void), thisArg?: any, options?: AddEventListenerOptions | boolean): void { super.addEventListener(arg, callback, thisArg, options); this._setTappable(this.hasListeners(Span.linkTapEvent)); } - removeEventListener(arg: string, callback?: (data: EventData) => void, thisArg?: any, options?: EventListenerOptions | boolean): void { + removeEventListener(arg: string, callback?: EventListenerOrEventListenerObject | ((data: EventData) => void), thisArg?: any, options?: EventListenerOptions | boolean): void { super.removeEventListener(arg, callback, thisArg, options); this._setTappable(this.hasListeners(Span.linkTapEvent)); }