@difizen/mana-observable
Version:
127 lines (117 loc) • 3.5 kB
text/typescript
/* eslint-disable @typescript-eslint/no-explicit-any */
import type { Disposable } from '@difizen/mana-common';
import { Event } from '@difizen/mana-common';
import { AsyncEmitter } from './async-event';
import { ObservableConfig } from './config';
import type { Notify } from './core';
import { ObservableSymbol } from './core';
import { Notifiable } from './notifiable';
import { Observability } from './utils';
export interface Notification<T = any> {
target: T;
prop?: any;
}
export class Notifier implements Disposable {
protected changedEmitter = new AsyncEmitter<Notification>();
disposed = false;
get onChange(): Event<any> {
if (ObservableConfig.async) {
return this.onChangeAsync;
}
return this.onChangeSync;
}
get onChangeAsync() {
return this.changedEmitter.eventAsync;
}
get onChangeSync() {
return this.changedEmitter.event;
}
dispose() {
this.changedEmitter.dispose();
this.disposed = true;
}
once(trigger: Notify, context?: any): Disposable {
const toDispose = this.onChange((e) => {
trigger(e.target, e.prop);
toDispose.dispose();
}, context);
return toDispose;
}
notify(target: any, prop?: any): void {
this.changedEmitter.fire({ target, prop });
if (prop) {
Notifier.trigger(target);
}
}
static trigger(target: any, prop?: any): void {
const exist = Notifier.get(target, prop);
if (exist) {
exist.notify(target, prop);
}
}
static getOrCreate(target: any, prop?: any): Notifier {
const notifier = Notifiable.getNotifier(target);
if (notifier) {
return notifier;
}
const origin = Observability.getOrigin(target);
const exist = Notifier.get(origin, prop);
if (exist && !exist.disposed) {
return exist;
}
const ntf = new Notifier();
Notifier.set(origin, ntf, prop);
return ntf;
}
static find(target: any, prop?: any): Notifier | undefined {
if (Observability.marked(target, prop) || Observability.canBeObservable(target)) {
return Notifier.getOrCreate(target, prop);
}
return undefined;
}
static get(target: any, property?: any): Notifier | undefined {
if (property === undefined) {
return Reflect.getMetadata(ObservableSymbol.Notifier, target);
} else {
return Reflect.getMetadata(ObservableSymbol.Notifier, target, property);
}
}
static set(target: any, notifier: Notifier, property?: any): void {
if (property === undefined) {
Reflect.defineMetadata(ObservableSymbol.Notifier, notifier, target);
} else {
Reflect.defineMetadata(ObservableSymbol.Notifier, notifier, target, property);
}
}
static once(notifier: Notifier, key: any, onChange: () => void) {
const toDispose = Observability.getDisposable(key, notifier);
if (toDispose) {
toDispose.dispose();
}
const disposable = notifier.once(() => {
onChange();
});
Observability.setDisposable(key, disposable, notifier);
}
/**
* Get event from target
* @param target
* @param prop
* @param async false by default
* @returns
*/
static toEvent(target: any, prop?: any, async: boolean | null = false) {
const notifier = Notifier.find(target, prop);
if (notifier) {
if (async === null) {
return notifier.onChange;
}
if (async) {
return notifier.onChangeAsync;
} else {
return notifier.onChangeSync;
}
}
return Event.None;
}
}