/**
 * Advanced React links for purely functional two-way data binding
 *
 * MIT License, (c) 2016 Vlad Balin, Volicon.
 */ import { helpers , arrayHelpers , objectHelpers } from './helpers' ; export type Transform < T > = ( value : T , event ? : { } ) => T ; export type EventHandler = ( event : { } ) => void ; export interface Validator < T > { ( value ) : boolean ; error ? : any ; } export { StateRef as Link } ; export abstract class StateRef < T > { static value < T > ( value , set ) { return new CustomStateRef ( value , set ) ; } /**
    * Unwrap object with links, returning an object of a similar shape filled with link values.
    */ static getValues < K extends keyof L , L extends RefsHash > ( links ) { return unwrap ( links , 'value' ) as any ; } get current ( ) { return this . value ; } set current ( x ) { this . set ( x ) ; } private get _changeToken ( ) { return this . value ; } /**
     * Unwrap object with links, returning an object of a similar shape filled with link errors.
     */ static getErrors < K extends keyof L , L extends RefsHash > ( links ) { return unwrap ( links , 'error' ) as any ; } /**
     * Return true if an object with links contains any errors.
     */ static hasErrors < L extends RefsHash > ( links ) { for ( let key in links ) { if ( links . hasOwnProperty ( key ) && links [ key ] . error ) { return true ; } } return false ; } /**
    * Assing links with values from the source object.
    */ static setValues ( links , source ) { if ( source ) { for ( let key in links ) { const sourceKey = trim ( key ) ; if ( source . hasOwnProperty ( sourceKey ) ) { const sourceVal = source [ sourceKey ] ; sourceVal === void 0 || links [ key ] . set ( sourceVal ) ; } } } } constructor ( public value ) { } error : any ; abstract set ( x ) ; onChange ( handler ) { return new ClonedStateRef ( this , ( x ) => { handler ( x ) ; this . set ( x ) ; } ) ; } get props ( ) { return typeof this . value === 'boolean' ? { checked : this . value , onChange : e => this . set ( Boolean ( e . target . checked ) as any ) } : { value : this . value , onChange : e => this . set ( e . target . value ) } ; } update ( transform , e ? ) { const next = transform ( this . clone ( ) , e ) ; next === void 0 || this . set ( next ) ; } pipe ( handler ) { return new ClonedStateRef ( this , x => { const next = handler ( x , this . value ) ; next === void 0 || this . set ( next ) ; } ) ; } action ( transform ) { return e => this . update ( transform , e ) ; } equals ( truthyValue ) { return new EqualsRef ( this , truthyValue ) ; } enabled ( defaultValue ? ) { return new EnabledRef ( this , defaultValue || "" ) ; } contains < E > ( this , element ) { return new ContainsRef ( this , element ) ; } push < E > ( this , ... args ) ; push ( ) { const array = arrayHelpers . clone ( this . value ) ; Array . prototype . push . apply ( array , arguments ) ; this . set ( array ) ; } unshift < E > ( this , ... args ) ; unshift ( ) { const array = arrayHelpers . clone ( this . value ) ; Array . prototype . unshift . apply ( array , arguments ) ; this . set ( array ) ; } splice ( start , deleteCount ? ) ; splice ( ) { const array = arrayHelpers . clone ( this . value ) ; Array . prototype . splice . apply ( array , arguments ) ; this . set ( array ) ; } map < E , Z > ( this , iterator ) ; map < E , Z > ( this , iterator ) ; map ( iterator ) { return helpers ( this . value ) . map ( this , iterator ) ; } removeAt < E > ( this , key ) ; removeAt < E > ( this , key ) ; removeAt ( key ) { const { value } = this , _ = helpers ( value ) ; this . set ( _ . remove ( _ . clone ( value ) , key ) ) ; } at < E > ( this , key ) ; at < K extends keyof T , E extends T [ K ] > ( key ) ; at ( key ) { return new RefAt ( this , key ) ; } clone ( ) { let { value } = this ; return helpers ( value ) . clone ( value ) ; } /**
     * Convert link to object to the object of links. Optionally filter by
     */ pick < K extends keyof T > ( ... keys ) ; pick ( ) { let links = { } , keys = arguments . length ? arguments : Object . keys ( this . value ) ; for ( let i = 0 ; i < keys . length ; i ++ ) { const key = keys [ i ] ; links [ key ] = new RefAt ( this , key ) ; } return links ; } /**
     * Convert link to object to the object of links with $-keys.
     */ $links ( ) { let links = { } , { value } = this ; for ( let key in value ) { if ( value . hasOwnProperty ( key ) ) { links [ '$' + key ] = new RefAt ( this , key ) ; } } return links as any ; } /**
     * Validate link with validness predicate and optional custom error object. Can be chained.
     */ check ( whenValid , error ? ) { if ( ! this . error && ! whenValid ( this . value ) ) { this . error = error || whenValid . error || defaultError ; } return this ; } } export class CustomStateRef < T > extends StateRef < T > { set ( x ) { } constructor ( value , set ) { super ( value ) ; this . set = set ; } } export class ClonedStateRef < T > extends StateRef < T > { set ( x ) { } constructor ( parent , set ) { super ( parent . value ) ; this . set = set ; const { error } = parent ; if ( error ) this . error = error ; } } export class EqualsRef extends StateRef < boolean > { constructor ( public parent , public truthyValue ) { super ( parent . value === truthyValue ) ; } set ( x ) { this . parent . set ( x ? this . truthyValue : null ) ; } } export class EnabledRef extends StateRef < boolean > { constructor ( public parent , public defaultValue ) { super ( parent . value != null ) ; } set ( x ) { this . parent . set ( x ? this . defaultValue : null ) ; } } export class ContainsRef extends StateRef < boolean > { constructor ( public parent , public element ) { super ( parent . value . indexOf ( element ) >= 0 ) ; } set ( x ) { var next = Boolean ( x ) ; if ( this . value !== next ) { var arr = this . parent . value , nextValue = x ? arr . concat ( this . element ) : arr . filter ( el => el !== this . element ) ; this . parent . set ( nextValue ) ; } } } const defaultError = 'Invalid value' ; /**
 * Link to array or object element enclosed in parent link.
 * Performs purely functional update of the parent, shallow copying its value on `set`.
 */ export class RefAt < E , K > extends StateRef < E > { constructor ( private parent , public key ) { super ( parent . value [ key ] ) ; } remove ( ) { this . parent . removeAt ( < any > this . key ) ; } set ( x ) { if ( this . value !== x ) { this . parent . update ( value => { value [ this . key ] = x ; return value ; } ) ; } } ; } export interface RefsHash { [ name ] : StateRef < any > ; } function unwrap ( links , field ) { const values = { } ; for ( let key in links ) { if ( links . hasOwnProperty ( key ) ) { const value = links [ key ] [ field ] ; if ( value !== void 0 ) { values [ trim ( key ) ] = value ; } } } return values ; } function trim ( key ) { return key [ 0 ] === '$' ? key . slice ( 1 ) : key ; }