import { $ALIVE , $O , IDisposable , isFunction , shallowCopy } from '../shared' ; import { Change , IChangeTarget } from './Change' ; import { commit } from './commit' ; import { getDebug } from './debug' ; /**
 * Observers are disposable "change targets".
 *
 * Use `IChangeTarget<T>` if your observer is not disposable.
 */ export interface IObserver < T = any > extends IChangeTarget < T > , IDisposable { } /** The source of truth for an observable */ type Source < T = any > = ( ( ) => T ) | T ; let nextId = 1 ; /** An observable value */ export class Observable < T = any > implements IObserver < T > { [ '_observers' ] ? : Array < IChangeTarget < T > > = undefined ; protected _get ? : ( ) => T ; protected _value ? : T ; readonly id : number ; constructor ( source ) { this . id = nextId ++ ; this . _rebind ( source ) ; } /** Try coercing the given value into an Observable instance. */ static from < T = any > ( value ) { return ( ( value && ( value [ $O ] || ( value instanceof Observable && value ) ) ) || null ) ; } /** The current value. */ get ( ) { return this . _get ? this . _get ( ) : this . _value ! ; } /**
     * When the source of truth is a getter, this method only notifies observers.
     * Otherwise, this method also updates the current value.
     */ set ( newValue , force ? ) { let oldValue = this . get ( ) ; if ( force || newValue !== oldValue ) { if ( ! this . _get ) { this . _value = newValue ; } commit ( this , oldValue , newValue ) ; } } /** Listen for changes. */ tap ( onUpdate ) { type ITapper = IObserver < T > & { onUpdate : typeof onUpdate ; } ; let observer = { onUpdate , dispose : ( ) => this . _removeObserver ( observer ) , _onChange : change => change . prop === null && onUpdate ( change . newValue as any ) , } ; this . _addObserver ( observer ) ; return observer ; } /** Remove all observers and perform subclass cleanup (if necessary) */ dispose ( ) { if ( this . _observers ) { this . _observers = undefined ; this . deactivate ( ) ; } } internal [ '_onChange' ] ( change ) { let observers = this . _observers ; if ( getDebug ( this ) ) { console . debug ( '%s(%s) changed: (observers: %O, change: %O)' , this . constructor . name , this . id , observers ? observers . length : 0 , shallowCopy ( change ) ) ; } if ( observers ) { for ( let observer of observers . slice ( 0 ) ) { observer . _onChange ( change ) ; } } } internal [ '_isCurrent' ] ( value ) { return value === this . get ( ) ; } internal [ '_rebind' ] ( source ) { this . _get = isFunction ( source ) ? source : undefined ; this . _value = this . _get ? undefined : ( source as any ) ; } internal [ '_addObserver' ] ( observer , prepend ? ) { if ( this . _observers ) { this . _observers [ prepend ? 'unshift' : 'push' ] ( observer ) ; } else { this . activate ( ) ; this . _observers = [ observer ] ; } return observer ; } internal [ '_removeObserver' ] ( observer ) { let observers = this . _observers ; if ( observers ) { let index = observers . indexOf ( observer ) ; if ( index < 0 ) return ; if ( observers . length > 1 ) { observers . splice ( index , 1 ) ; } else { this . _observers = undefined ; this . deactivate ( ) ; } } } /** Called when an Observable goes from 0 observers to 1+ */ protected activate ( ) { } /** Called when an Observable goes from 1+ observers to 0 */ protected deactivate ( ) { } } /** Returns true if the given value has been passed to `o()` and is not stale. */ export function isObservable ( value ) { if ( value ) { let observable = value [ $O ] ; if ( observable ) { return observable . _isCurrent ( value ) ; } } return false ; } /** Throws an error when the given object is an old revision */ export function getObservable < T = any > ( value ) { let observable = value [ $O ] ; if ( observable ) { if ( ! value [ $ALIVE ] && ! observable . _isCurrent ( value ) ) { throw Error ( 'Outdated values cannot be observed' ) ; } return observable ; } } internal export function getObservers ( value ) { let observable = Observable . from ( value ) ; if ( observable ) { return observable . _observers ; } throw Error ( 'Not observable' ) ; }