import { Behavior , Future , isBehavior , Now , placeholder , runNow , sinkBehavior , sinkFuture , Stream , instant , Time , tick } from "@funkia/hareactive" ; import { render } from "@funkia/hareactive/dom" ; import { Monad , monad } from "@funkia/jabz" ; import { copyRemaps , id , Merge , mergeObj } from "./utils" ; const supportsProxy = typeof Proxy !== "undefined" ; export type Showable = string | number | boolean ; function isShowable ( s ) { return ( typeof s === "string" || typeof s === "number" || typeof s === "boolean" ) ; } export interface DomApi { appendChild ( child ) : void ; insertBefore ( insert , before ) : void ; removeChild ( child ) : void ; } export type Out < A , O > = { available : A ; output : O ; } ; /**
 * A component is a function from a parent DOM node to a now
 * computation I.e. something like `type Component<A> = (p: Node) =>
 * Now<A>`. We don't define it as a type alias because we want to
 * make it a monad in different way than Now.
 */ @ monad export abstract class Component < A , O > implements Monad < O > { static of < B > ( b ) { return new OfComponent ( b ) ; } of < P > ( p ) { return new OfComponent ( p ) ; } flatMap < B , P > ( f ) { return new FlatMapComponent ( this , f ) ; } chain < B , P > ( f ) { return new FlatMapComponent ( this , f ) ; } use < P > ( f ) ; use < B extends Record < string , keyof A > > ( remaps ) ; use ( handler ) { if ( typeof handler === "function" ) { return new HandleOutput ( ( a , o ) => ( { available : a , output : mergeObj ( mergeObj ( { } , handler ( a ) ) , o ) } ) , this ) ; } else { return new HandleOutput ( ( a , o ) => ( { available : a , output : mergeObj ( mergeObj ( { } , o ) , copyRemaps ( handler , a ) ) } ) , this ) ; } } output < R > ( o ) { return { available : o , component : this } ; } view ( ) { return view ( this ) ; } static multi : boolean = false ; multi : boolean = false ; abstract run ( parent , destroyed , time ) ; flatten : < B , P > ( this : Component < A , Component < B , P > > ) => Component < A , P > ; map : < P > ( f : ( a : O ) => P ) => Component < A , P > ; mapTo : < P > ( b : P ) => Component < A , P > ; ap : < P > ( a : Component < A , ( o : O ) => P > ) => Component < A , P > ; lift : ( f : Function , ... ms : any [ ] ) => Component < A , any > ; } class OfComponent < O > extends Component < { } , O > { constructor ( private readonly o ) { super ( ) ; } run ( _1 , _2 ) { return { output : this . o , available : { } } ; } } class PerformComponent < O > extends Component < { } , O > { constructor ( private cb ) { super ( ) ; } run ( _1 , _2 ) { return { available : { } , output : this . cb ( ) } ; } } /**
 * Takes a callback, potentially with side-effects. The callback is invoked when
 * the component is run and the return value becomes the components output.
 */ export function performComponent < O > ( callback ) { return new PerformComponent ( callback ) ; } export function liftNow < A > ( now ) { return performComponent ( ( ) => runNow ( now ) ) ; } class HandleOutput < A , O , B , P > extends Component < B , P > { constructor ( private readonly handler , private readonly c ) { super ( ) ; } run ( parent , destroyed , t ) { const { available , output } = this . c . run ( parent , destroyed , t ) ; return this . handler ( available , output ) ; } } /**
 * This type is sometimes useful to trick TypeScript into evaulating types
 * that it would otherwise not evaluating. This can make some types
 * significantly more readable.
 */ type Id < T extends object > = { } & { [ P in keyof T ] : T [ P ] ; } ; export type Remap < A extends Record < any , any > , B extends Record < any , keyof A > > = Id < { [ K in keyof B ] : A [ B [ K ] ] ; } > ; export function use < A , O , P > ( f , c ) ; export function use < A , O , B extends Record < string , keyof A > > ( remaps , c ) ; export function use < A > ( remaps , component ) { return component . use ( remaps ) ; } /**
 * An empty component that adds no elements to the DOM and produces an
 * empty object as output.
 */ export const emptyComponent = Component . of ( { } ) ; class FlatMapComponent < A , O , B , P > extends Component < A , P > { constructor ( private readonly component , private readonly f ) { super ( ) ; } run ( parent , destroyed , t ) { const { available , output : outputFirst } = this . component . run ( parent , destroyed , t ) ; const { output } = this . f ( outputFirst ) . run ( parent , destroyed , t ) ; return { available , output } ; } } param parent param component export function runComponent < A > ( parent , component , destroy = sinkFuture ( ) ) { if ( typeof parent === "string" ) { parent = document . querySelector ( parent ) ! ; } return toComponent ( component ) . run ( parent , destroy , tick ( ) ) . output ; } export function testComponent < A , O > ( c ) { const dom = document . createElement ( "div" ) ; const destroyed = sinkFuture < boolean > ( ) ; const { available , output } = c . run ( dom , destroyed , tick ( ) ) ; const destroy = destroyed . resolve . bind ( destroyed ) ; return { available , output , dom , destroy } ; } export function isComponent ( c ) { return c instanceof Component ; } type Reactive = Behavior < any > | Stream < any > | Future < any > ; export interface ReactivesObject { [ a ] : Reactive ; } /**
 * Removes all properties from a type except those that are either
 * streams, behaviors, or futures.
 */ type OnlyReactives < R > = { [ K in keyof R ] : R [ K ] extends Reactive ? R [ K ] : never ; } ; const placeholderProxyHandler = { get ( target , name ) { if ( ! ( name in target ) ) { target [ name ] = placeholder ( ) ; } return target [ name ] ; } } ; type Result < R , O > = { available : R ; component : Child < O > ; } ; function isLoopResult ( r ) { return typeof r === "object" && "available" in r && "component" in r ; } class LoopComponent < L , O > extends Component < O , { } > { constructor ( private f , private placeholderNames ? ) { super ( ) ; } run ( parent , destroyed , t ) { let placeholderObject = { destroyed } ; if ( this . placeholderNames !== undefined ) { for ( const name of this . placeholderNames ) { placeholderObject [ name ] = placeholder ( ) ; } } else if ( supportsProxy ) { placeholderObject = new Proxy ( placeholderObject , placeholderProxyHandler ) ; } else { throw new Error ( "component called with no list of names and proxies are not supported." ) ; } const res = instant ( ( start ) => this . f ( placeholderObject , start ) ) . run ( t ) ; const result = Now . is ( res ) ? res . run ( t ) : res ; const { available , component } = isLoopResult ( result ) ? result : { available : { } as O , component : result } ; const { output : looped } = toComponent ( component ) . run ( parent , destroyed , t ) ; const needed = Object . keys ( placeholderObject ) ; for ( const name of needed ) { if ( name === "destroyed" ) { continue ; } if ( looped [ name ] === undefined ) { throw new Error ( `The property ${ name } is missing.` ) ; } placeholderObject [ name ] . replaceWith ( looped [ name ] , t ) ; } return { available , output : { } } ; } } export function component < L extends ReactivesObject > ( f , placeholderNames ? ) ; export function component < L extends ReactivesObject , O > ( f , placeholderNames ? ) ; export function component < L , O extends ReactivesObject > ( f , placeholderNames ? ) { return new LoopComponent < L , O > ( f , placeholderNames ) ; } class MergeComponent < A , O extends object , B , P extends object > extends Component < { } , O & P > { constructor ( private c1 , private c2 ) { super ( ) ; } run ( parent , destroyed , t ) { const res1 = this . c1 . run ( parent , destroyed , t ) ; const res2 = this . c2 . run ( parent , destroyed , t ) ; return { available : { } , output : mergeObj ( mergeObj ( { } , res2 . output ) , res1 . output ) } ; } } /**
 * Merges two components. Their selected output is combined.
 */ export function merge < O extends object , A , P extends object , B > ( c1 , c2 ) { return new MergeComponent ( c1 , c2 ) ; } function addErrorHandler ( modelName , viewName , obj ) { if ( modelName === "" ) { modelName = "anonymous" ; } if ( viewName === "" ) { viewName = "anonymous" ; } if ( ! supportsProxy ) { return obj ; } return new Proxy ( obj , { get ( object , prop ) { if ( prop in obj ) { return object [ prop ] ; } throw new Error ( `The model, ${ modelName }, expected a property "${ prop }" but the view, ${ viewName }, returned an object without the property.` ) ; } } ) ; } class ModelViewComponent < M extends Record < string , any > , V > extends Component < M , { } > { constructor ( private args , private model , private viewF , private placeholderNames ? ) { super ( ) ; } run ( parent , destroyed , t ) { const { viewF , model , args } = this ; let placeholders ; if ( supportsProxy ) { placeholders = new Proxy ( { } , placeholderProxyHandler ) ; } else { placeholders = { } ; if ( this . placeholderNames !== undefined ) { for ( const name of this . placeholderNames ) { placeholders [ name ] = placeholder ( ) ; } } } const { output : viewOutput } = toComponent ( viewF ( placeholders , ... args ) ) . run ( parent , destroyed , t ) ; const helpfulViewOutput = addErrorHandler ( model . name , viewF . name , Object . assign ( viewOutput , { destroyed } ) ) ; const behaviors = runNow ( model ( helpfulViewOutput , ... args ) ) ; for ( const name of Object . keys ( behaviors ) ) { placeholders [ name ] . replaceWith ( behaviors [ name ] ) ; } return { available : behaviors , output : { } } ; } } export type Model < V , M > = ( v : V ) => Now < M > ; export type Model1 < V , M , A > = ( v : V , a : A ) => Now < M > ; export type View < M , V > = ( m : OnlyReactives < M > ) => Child < V > ; export type View1 < M , V , A > = ( m : OnlyReactives < M > , a : A ) => Child < V > ; export function modelView < M extends Record < string , any > , V > ( model , view , toViewReactiveNames ? ) ; export function modelView < M extends Record < string , any > , V , A > ( model , view , toViewReactiveNames ? ) ; export function modelView < M extends Record < string , any > , V > ( model , view , toViewReactiveNames ? ) { return ( ... args ) => new ModelViewComponent < M , V > ( args , model , view , toViewReactiveNames ) ; } export function view < O > ( c ) { return new HandleOutput ( ( _ , o ) => ( { available : o , output : { } } ) , c ) ; } export type CE < O = any > = Component < any , O > | Behavior < Component < any , any > > | Showable | Behavior < Showable > | ChildList ; export type Child < O = any > = [ CE ] | [ CE , CE ] | [ CE , CE , CE ] | [ CE , CE , CE , CE ] | [ CE , CE , CE , CE , CE ] | [ CE , CE , CE , CE , CE , CE ] | [ CE , CE , CE , CE , CE , CE , CE ] | [ CE , CE , CE , CE , CE , CE , CE , CE ] | [ CE , CE , CE , CE , CE , CE , CE , CE , CE ] | [ CE , CE , CE , CE , CE , CE , CE , CE , CE , CE ] | [ CE , CE , CE , CE , CE , CE , CE , CE , CE , CE , CE ] | [ CE , CE , CE , CE , CE , CE , CE , CE , CE , CE , CE , CE ] | CE < O > ; export interface ChildList extends Array < CE > { } /**
 * Takes a component type and returns the selected output of the component.
 */ export type ComponentSelectedOutput < C > = C extends Component < any , infer O > ? O : never ; /**
 * Takes a component type and returns the output of the component.
 */ export type ComponentOutput < C > = C extends Component < any , infer A > ? A : never ; export type ChildSelectedOutput < Ch extends Child > = ComponentSelectedOutput < ToComponent < Ch > > ; export type MC < C1 extends CE , C2 extends CE > = Component < ComponentSelectedOutput < TC < C1 > > & ComponentSelectedOutput < TC < C2 > > , Merge < ComponentSelectedOutput < TC < C1 > > & ComponentSelectedOutput < TC < C2 > > > > ; export type ArrayToComponent < A extends Array < Child > > = A extends [ CE ] ? TC < A [ 0 ] > : A extends [ CE , CE ] ? MC < A [ 0 ] , A [ 1 ] > : A extends [ CE , CE , CE ] ? MC < A [ 0 ] , MC < A [ 1 ] , A [ 2 ] > > : A extends [ CE , CE , CE , CE ] ? MC < A [ 0 ] , MC < A [ 1 ] , MC < A [ 2 ] , A [ 3 ] > > > : A extends [ CE , CE , CE , CE , CE ] ? MC < A [ 0 ] , MC < A [ 1 ] , MC < A [ 2 ] , MC < A [ 3 ] , A [ 4 ] > > > > : A extends [ CE , CE , CE , CE , CE , CE ] ? MC < A [ 0 ] , MC < A [ 1 ] , MC < A [ 2 ] , MC < A [ 3 ] , MC < A [ 4 ] , A [ 5 ] > > > > > : A extends [ CE , CE , CE , CE , CE , CE , CE ] ? TC < MC < A [ 0 ] , MC < A [ 1 ] , MC < A [ 2 ] , MC < A [ 3 ] , MC < A [ 4 ] , MC < A [ 5 ] , A [ 6 ] > > > > > > > : A extends [ CE , CE , CE , CE , CE , CE , CE , CE ] ? TC < MC < A [ 0 ] , MC < A [ 1 ] , MC < A [ 2 ] , MC < A [ 3 ] , MC < A [ 4 ] , MC < A [ 5 ] , MC < A [ 6 ] , A [ 7 ] > > > > > > > > : A extends [ CE , CE , CE , CE , CE , CE , CE , CE , CE ] ? TC < MC < A [ 0 ] , MC < A [ 1 ] , MC < A [ 2 ] , MC < A [ 3 ] , MC < A [ 4 ] , MC < A [ 5 ] , MC < A [ 6 ] , MC < A [ 7 ] , A [ 8 ] > > > > > > > > > : A extends [ CE , CE , CE , CE , CE , CE , CE , CE , CE , CE ] ? TC < MC < A [ 0 ] , MC < A [ 1 ] , MC < A [ 2 ] , MC < A [ 3 ] , MC < A [ 4 ] , MC < A [ 5 ] , MC < A [ 6 ] , MC < A [ 7 ] , MC < A [ 8 ] , A [ 9 ] > > > > > > > > > > : A extends [ CE , CE , CE , CE , CE , CE , CE , CE , CE , CE , CE ] ? TC < MC < A [ 0 ] , MC < A [ 1 ] , MC < A [ 2 ] , MC < A [ 3 ] , MC < A [ 4 ] , MC < A [ 5 ] , MC < A [ 6 ] , MC < A [ 7 ] , MC < A [ 8 ] , MC < A [ 9 ] , A [ 10 ] > > > > > > > > > > > : A extends [ CE , CE , CE , CE , CE , CE , CE , CE , CE , CE , CE , CE ] ? TC < MC < A [ 0 ] , MC < A [ 1 ] , MC < A [ 2 ] , MC < A [ 3 ] , MC < A [ 4 ] , MC < A [ 5 ] , MC < A [ 6 ] , MC < A [ 7 ] , MC < A [ 8 ] , MC < A [ 9 ] , MC < A [ 10 ] , A [ 11 ] > > > > > > > > > > > > : A extends [ CE , CE , CE , CE , CE , CE , CE , CE , CE , CE , CE , CE , CE ] ? TC < MC < A [ 0 ] , MC < A [ 1 ] , MC < A [ 2 ] , MC < A [ 3 ] , MC < A [ 4 ] , MC < A [ 5 ] , MC < A [ 6 ] , MC < A [ 7 ] , MC < A [ 8 ] , MC < A [ 9 ] , MC < A [ 10 ] , MC < A [ 11 ] , A [ 12 ] > > > > > > > > > > > > > : Component < any , any > ; export type TC < A > = A extends Component < any , infer O > ? Component < { } , O > : A extends Showable ? Component < { } , { } > : A extends Behavior < Showable > ? Component < { } , { } > : Component < any , any > ; export type ToComponent < A > = A extends Child [ ] ? ArrayToComponent < A > : TC < A > ; export function isChild ( a ) { return isComponent ( a ) || isBehavior ( a ) || isShowable ( a ) || Array . isArray ( a ) ; } class TextComponent extends Component < { } , { } > { constructor ( private t ) { super ( ) ; } run ( parent , destroyed ) { const node = document . createTextNode ( this . t . toString ( ) ) ; parent . appendChild ( node ) ; destroyed . subscribe ( ( toplevel ) => { if ( toplevel ) { parent . removeChild ( node ) ; } } ) ; return { available : { } , output : { } } ; } } export function text ( showable ) { return new TextComponent ( showable ) ; } class ListComponent extends Component < any , any > { components : Component < any , any > [ ] ; constructor ( children ) { super ( ) ; this . components = [ ] ; for ( const child of children ) { const component = toComponent ( child ) ; this . components . push ( component ) ; } } run ( parent , destroyed , t ) { let output = { } ; for ( let i = 0 ; i < this . components . length ; ++ i ) { const component = this . components [ i ] ; const res = component . run ( parent , destroyed , t ) ; mergeObj ( output , res . output ) ; } return { available : output , output } ; } } export function toComponent < A extends Child > ( child ) { if ( isComponent ( child ) ) { return child as any ; } else if ( isBehavior ( child ) ) { return dynamic ( child ) . mapTo ( { } ) as any ; } else if ( isShowable ( child ) ) { return text ( child ) as any ; } else if ( Array . isArray ( child ) ) { return new ListComponent ( child ) as any ; } else { throw new Error ( "Child could not be converted to component" ) ; } } class FixedDomPosition implements DomApi { end : Comment ; constructor ( private parent , destroy ) { this . end = document . createComment ( "Fixed point" ) ; parent . appendChild ( this . end ) ; destroy . subscribe ( ( ) => parent . removeChild ( this . end ) ) ; } appendChild ( child ) { this . parent . insertBefore ( child , this . end ) ; } insertBefore ( e , a ) { this . parent . insertBefore ( e , a ) ; } removeChild ( c ) { this . parent . removeChild ( c ) ; } } class DynamicComponent < O > extends Component < Behavior < O > , { } > { constructor ( private behavior ) { super ( ) ; } run ( parent , dynamicDestroyed , t ) { let destroyPrevious ; const parentWrap = new FixedDomPosition ( parent , dynamicDestroyed ) ; const available = this . behavior . map ( ( child ) => { if ( destroyPrevious !== undefined ) { destroyPrevious . resolve ( true ) ; } destroyPrevious = sinkFuture < boolean > ( ) ; const { output } = toComponent ( child ) . run ( parentWrap , destroyPrevious . combine ( dynamicDestroyed ) , t ) ; return output ; } ) ; render ( id , available , t ) ; return { available , output : { } } ; } } export function dynamic < O > ( behavior ) ; export function dynamic < O > ( behavior ) ; export function dynamic < O > ( behavior ) { return new DynamicComponent ( behavior ) ; } class DomRecorder implements DomApi { constructor ( private parent ) { } elms : Node [ ] = [ ] ; appendChild ( child ) { this . parent . appendChild ( child ) ; this . elms . push ( child ) ; } insertBefore ( a , b ) { this . parent . insertBefore ( a , b ) ; const index = this . elms . indexOf ( b ) ; this . elms . splice ( index , 0 , a ) ; } removeChild ( c ) { this . parent . removeChild ( c ) ; const index = this . elms . indexOf ( c ) ; this . elms . splice ( index , 1 ) ; } } type ComponentInfo < O > = { output : O ; destroy : Future < boolean > ; elms : Node [ ] ; } ; class ComponentList < A , O > extends Component < Behavior < O [ ] > , { } > { constructor ( private compFn , private listB , private getKey ) { super ( ) ; } run ( parent , listDestroyed , t ) { const resultB = sinkBehavior < O [ ] > ( [ ] ) ; let keyToElm = { } ; const parentWrap = new FixedDomPosition ( parent , listDestroyed ) ; this . listB . subscribe ( ( newAs ) => { const newKeyToElm = { } ; const newArray = [ ] ; for ( let i = 0 ; i < newAs . length ; i ++ ) { const a = newAs [ i ] ; const key = this . getKey ( a , i ) ; let info = keyToElm [ key ] ; if ( info === undefined ) { const destroy = sinkFuture < boolean > ( ) ; const recorder = new DomRecorder ( parentWrap ) ; const { output } = this . compFn ( a ) . run ( recorder , destroy . combine ( listDestroyed ) , t ) ; info = { elms : recorder . elms , output , destroy } ; } else { for ( const elm of info . elms ) { parentWrap . appendChild ( elm ) ; } } newArray . push ( info . output ) ; newKeyToElm [ key ] = info ; } const oldKeys = Object . keys ( keyToElm ) ; for ( const key of oldKeys ) { if ( newKeyToElm [ key ] === undefined ) { keyToElm [ key ] . destroy . resolve ( true ) ; } } keyToElm = newKeyToElm ; resultB . push ( newArray ) ; } ) ; return { available : resultB , output : { } } ; } } export function list < A extends string | number , O > ( componentCreator , listB , getKey ? ) ; export function list < A , O > ( componentCreator , listB , getKey ) ; export function list < A , O > ( componentCreator , listB , getKey = id as any ) { return new ComponentList ( componentCreator , listB , getKey ) ; }