import { Driver , FantasyObservable } from '@cycle/run' ; import { init , Module , VNode , toVNode } from 'snabbdom' ; import xs , { Stream , Listener } from 'xstream' ; import concat from 'xstream/extra/concat' ; import sampleCombine from 'xstream/extra/sampleCombine' ; import { DOMSource } from './DOMSource' ; import { MainDOMSource } from './MainDOMSource' ; import { VNodeWrapper } from './VNodeWrapper' ; import { getValidNode , checkValidContainer } from './utils' ; import defaultModules from './modules' ; import { IsolateModule } from './IsolateModule' ; import { EventDelegator } from './EventDelegator' ; function makeDOMDriverInputGuard ( modules ) { if ( ! Array . isArray ( modules ) ) { throw new Error ( `Optional modules option must be an array for snabbdom modules` ) ; } } function domDriverInputGuard ( view$ ) { if ( ! view$ || typeof view$ . addListener !== `function` || typeof view$ . fold !== `function` ) { throw new Error ( `The DOM driver function expects as input a Stream of ` + `virtual DOM elements` ) ; } } export interface DOMDriverOptions { modules ? : Array < Partial < Module > > ; reportSnabbdomError ? ( err ) : void ; } function dropCompletion < T > ( input ) { return xs . merge ( input , xs . never ( ) ) ; } function unwrapElementFromVNode ( vnode ) { return vnode . elm as Element ; } function defaultReportSnabbdomError ( err ) { ( console . error || console . log ) ( err ) ; } function makeDOMReady$ ( ) { return xs . create < null > ( { start ( lis ) { if ( document . readyState === 'loading' ) { document . addEventListener ( 'readystatechange' , ( ) => { const state = document . readyState ; if ( state === 'interactive' || state === 'complete' ) { lis . next ( null ) ; lis . complete ( ) ; } } ) ; } else { lis . next ( null ) ; lis . complete ( ) ; } } , stop ( ) { } , } ) ; } function addRootScope ( vnode ) { vnode . data = vnode . data || { } ; vnode . data . isolate = [ ] ; return vnode ; } function makeDOMDriver ( container , options = { } ) { checkValidContainer ( container ) ; const modules = options . modules || defaultModules ; makeDOMDriverInputGuard ( modules ) ; const isolateModule = new IsolateModule ( ) ; const patch = init ( [ isolateModule . createModule ( ) as Partial < Module > ] . concat ( modules ) ) ; const domReady$ = makeDOMReady$ ( ) ; let vnodeWrapper ; let mutationObserver ; const mutationConfirmed$ = xs . create < null > ( { start ( listener ) { mutationObserver = new MutationObserver ( ( ) => listener . next ( null ) ) ; } , stop ( ) { mutationObserver . disconnect ( ) ; } , } ) ; function DOMDriver ( vnode$ , name = 'DOM' ) { domDriverInputGuard ( vnode$ ) ; const sanitation$ = xs . create < null > ( ) ; const firstRoot$ = domReady$ . map ( ( ) => { const firstRoot = getValidNode ( container ) || document . body ; vnodeWrapper = new VNodeWrapper ( firstRoot ) ; return firstRoot ; } ) ; const rememberedVNode$ = vnode$ . remember ( ) ; rememberedVNode$ . addListener ( { } ) ; mutationConfirmed$ . addListener ( { } ) ; const elementAfterPatch$ = firstRoot$ . map ( firstRoot => xs . merge ( rememberedVNode$ . endWhen ( sanitation$ ) , sanitation$ ) . map ( vnode => vnodeWrapper . call ( vnode ) ) . startWith ( addRootScope ( toVNode ( firstRoot ) ) ) . fold ( patch , toVNode ( firstRoot ) ) . drop ( 1 ) . map ( unwrapElementFromVNode ) . startWith ( firstRoot as any ) . map ( el => { mutationObserver . observe ( el , { childList : true , attributes : true , characterData : true , subtree : true , attributeOldValue : true , characterDataOldValue : true , } ) ; return el ; } ) . compose ( dropCompletion ) ) . flatten ( ) ; const rootElement$ = concat ( domReady$ , mutationConfirmed$ ) . endWhen ( sanitation$ ) . compose ( sampleCombine ( elementAfterPatch$ ) ) . map ( arr => arr [ 1 ] ) . remember ( ) ; rootElement$ . addListener ( { error : options . reportSnabbdomError || defaultReportSnabbdomError , } ) ; const delegator = new EventDelegator ( rootElement$ , isolateModule ) ; return new MainDOMSource ( rootElement$ , sanitation$ , [ ] , isolateModule , delegator , name ) ; } return DOMDriver as any ; } export { makeDOMDriver } ;