mirror of
https://github.com/NativeScript/NativeScript.git
synced 2025-11-05 13:26:48 +08:00
feat: implement Event and EventTarget
This commit is contained in:
@@ -10,7 +10,7 @@ const timeOrigin = Date.now();
|
|||||||
*/
|
*/
|
||||||
const emptyArray = [] as const;
|
const emptyArray = [] as const;
|
||||||
|
|
||||||
export class DOMEvent {
|
export class DOMEvent implements Event {
|
||||||
/**
|
/**
|
||||||
* @private
|
* @private
|
||||||
* Internal API to facilitate testing - to be removed once we've completed
|
* 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
|
// From CustomEvent rather than Event. Can consider factoring out this
|
||||||
// aspect into DOMCustomEvent.
|
// aspect into DOMCustomEvent.
|
||||||
private readonly detail: unknown | null;
|
readonly detail: unknown | null;
|
||||||
|
|
||||||
private propagationState: EventPropagationState = EventPropagationState.resume;
|
private propagationState: EventPropagationState = EventPropagationState.resume;
|
||||||
|
|
||||||
|
|||||||
@@ -1,8 +1,6 @@
|
|||||||
import type { ViewBase } from '../../ui/core/view-base';
|
import type { ViewBase } from '../../ui/core/view-base';
|
||||||
import { DOMEvent } from '../dom-events/dom-event';
|
import { DOMEvent } from '../dom-events/dom-event';
|
||||||
|
|
||||||
import { Observable as ObservableDefinition, WrappedValue as WrappedValueDefinition } from '.';
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Base event data.
|
* Base event data.
|
||||||
*/
|
*/
|
||||||
@@ -51,7 +49,7 @@ let _wrappedIndex = 0;
|
|||||||
* By default property change will not be fired for a same object.
|
* 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.
|
* 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.
|
* Creates an instance of WrappedValue object.
|
||||||
* @param wrapped - the real value which should be wrapped.
|
* @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,
|
* 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.
|
* 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.
|
* 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 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.
|
* @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') {
|
if (typeof eventNames !== 'string') {
|
||||||
throw new TypeError('Events name(s) must be string.');
|
throw new TypeError('Events name(s) must be string.');
|
||||||
}
|
}
|
||||||
|
|
||||||
if (typeof callback !== 'function') {
|
if (typeof callback !== 'function') {
|
||||||
throw new TypeError('callback must be function.');
|
throw new TypeError('Callback must be function.');
|
||||||
}
|
}
|
||||||
|
|
||||||
const events = eventNames.trim().split(eventDelimiterPattern);
|
const events = eventNames.trim().split(eventDelimiterPattern);
|
||||||
for (let i = 0, l = events.length; i < l; i++) {
|
for (let i = 0, l = events.length; i < l; i++) {
|
||||||
const event = events[i];
|
const event = events[i];
|
||||||
const list = this.getEventList(event, true);
|
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.
|
// Don't allow addition of duplicate event listeners.
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Performance optimization - if we do not have the thisArg specified, do not wrap the callback in additional object (ObserveEntry)
|
// TODO: Performance optimization - if we do not have the thisArg specified, do not wrap the callback in additional object (ObserveEntry)
|
||||||
list.push({
|
list.push({
|
||||||
callback,
|
callback: callback as (data: EventData) => void,
|
||||||
thisArg,
|
thisArg,
|
||||||
...normalizeEventOptions(options),
|
...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 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.
|
* @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') {
|
if (typeof eventNames !== 'string') {
|
||||||
throw new TypeError('Events name(s) must be 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);
|
const list = this.getEventList(event, false);
|
||||||
if (list) {
|
if (!list) {
|
||||||
const index = Observable._indexOfListener(list, callback, thisArg, options);
|
continue;
|
||||||
if (index >= 0) {
|
}
|
||||||
list.splice(index, 1);
|
|
||||||
}
|
const index = Observable._indexOfListener(list, callback as (data: EventData) => void, thisArg, options);
|
||||||
if (list.length === 0) {
|
if (index >= 0) {
|
||||||
delete this._observers[event];
|
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);
|
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') {
|
if (typeof eventName !== 'string') {
|
||||||
throw new TypeError('Event must be string.');
|
throw new TypeError('Event must be string.');
|
||||||
}
|
}
|
||||||
|
|
||||||
if (callback && typeof callback !== 'function') {
|
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;
|
const eventClass = this.name === 'Observable' ? '*' : this.name;
|
||||||
@@ -278,7 +278,7 @@ export class Observable implements ObservableDefinition {
|
|||||||
|
|
||||||
const events = _globalEventHandlers[eventClass][eventName];
|
const events = _globalEventHandlers[eventClass][eventName];
|
||||||
if (callback) {
|
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) {
|
if (index >= 0) {
|
||||||
events.splice(index, 1);
|
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') {
|
if (typeof eventName !== 'string') {
|
||||||
throw new TypeError('Event must be string.');
|
throw new TypeError('Event must be string.');
|
||||||
}
|
}
|
||||||
|
|
||||||
if (typeof callback !== 'function') {
|
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;
|
const eventClass = this.name === 'Observable' ? '*' : this.name;
|
||||||
@@ -317,13 +317,13 @@ export class Observable implements ObservableDefinition {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const list = _globalEventHandlers[eventClass][eventName];
|
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.
|
// Don't allow addition of duplicate event listeners.
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
_globalEventHandlers[eventClass][eventName].push({
|
_globalEventHandlers[eventClass][eventName].push({
|
||||||
callback,
|
callback: callback as (data: EventData) => void,
|
||||||
thisArg,
|
thisArg,
|
||||||
...normalizeEventOptions(options),
|
...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[] {
|
private _getGlobalEventHandlers(data: EventData, eventType: 'First' | ''): ListenerEntry[] {
|
||||||
const eventClass = data.object?.constructor?.name;
|
const eventClass = data.object?.constructor?.name;
|
||||||
const globalEventHandlersForOwnClass = _globalEventHandlers[eventClass]?.[`${data.eventName}${eventType}`] ?? [];
|
const globalEventHandlersForOwnClass = _globalEventHandlers[eventClass]?.[`${data.eventName}${eventType}`] ?? [];
|
||||||
|
|||||||
@@ -292,7 +292,11 @@ export abstract class ViewCommon extends ViewBase implements ViewDefinition {
|
|||||||
return this._gestureObservers[type] || [];
|
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
|
// To avoid a full refactor of the Gestures system when migrating to DOM
|
||||||
// Events, we mirror the this._gestureObservers record, creating
|
// Events, we mirror the this._gestureObservers record, creating
|
||||||
// corresponding DOM Event listeners for each gesture.
|
// corresponding DOM Event listeners for each gesture.
|
||||||
@@ -308,7 +312,7 @@ export abstract class ViewCommon extends ViewBase implements ViewDefinition {
|
|||||||
// the same time).
|
// the same time).
|
||||||
|
|
||||||
if (typeof arg === 'number') {
|
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);
|
super.addEventListener(gestureToString(arg), callback, thisArg, options);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -320,15 +324,19 @@ export abstract class ViewCommon extends ViewBase implements ViewDefinition {
|
|||||||
for (const event of events) {
|
for (const event of events) {
|
||||||
const gesture = gestureFromString(event);
|
const gesture = gestureFromString(event);
|
||||||
if (gesture && !this._isEvent(arg)) {
|
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);
|
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') {
|
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);
|
super.removeEventListener(gestureToString(arg), callback, thisArg, options);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -338,7 +346,7 @@ export abstract class ViewCommon extends ViewBase implements ViewDefinition {
|
|||||||
for (const event of events) {
|
for (const event of events) {
|
||||||
const gesture = gestureFromString(event);
|
const gesture = gestureFromString(event);
|
||||||
if (gesture && !this._isEvent(arg)) {
|
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);
|
super.removeEventListener(event, callback, thisArg, options);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ export abstract class ScrollViewBase extends ContentView implements ScrollViewDe
|
|||||||
public scrollBarIndicatorVisible: boolean;
|
public scrollBarIndicatorVisible: boolean;
|
||||||
public isScrollEnabled: 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);
|
super.addEventListener(arg, callback, thisArg, options);
|
||||||
|
|
||||||
if (arg === ScrollViewBase.scrollEvent) {
|
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);
|
super.removeEventListener(arg, callback, thisArg, options);
|
||||||
|
|
||||||
if (arg === ScrollViewBase.scrollEvent) {
|
if (arg === ScrollViewBase.scrollEvent) {
|
||||||
|
|||||||
@@ -81,12 +81,12 @@ export class Span extends ViewBase implements SpanDefinition {
|
|||||||
return this._tappable;
|
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);
|
super.addEventListener(arg, callback, thisArg, options);
|
||||||
this._setTappable(this.hasListeners(Span.linkTapEvent));
|
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);
|
super.removeEventListener(arg, callback, thisArg, options);
|
||||||
this._setTappable(this.hasListeners(Span.linkTapEvent));
|
this._setTappable(this.hasListeners(Span.linkTapEvent));
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user