export interface VElement { nodeName : string ; attributes : VAttributes ; children : VNode [ ] ; key ? : string | number ; } export type VNode = VElement | string | number ; export interface VAttributes extends VLifecycle { key ? : string | number ; } export interface VLifecycle { onAttached ? ( el , attrs ? ) : void ; onBeforeUpdate ? ( el , attrs ? ) : void ; onUpdated ? ( el , attrs ? ) : void ; onBeforeRemove ? ( el ) : Promise < void > ; onRemoved ? ( el ) : void ; } export function isVElement ( vnode ? ) { return vnode && vnode [ "attributes" ] ; } function squash ( children ) { var squashed = [ ] ; for ( var x of children ) if ( ! Array . isArray ( x ) ) { if ( x !== null && x !== undefined ) squashed . push ( x ) ; } else for ( var y of squash ( x ) ) squashed . push ( y ) ; return squashed ; } export function createVElement ( name , attributes , ... children ) { children = squash ( children ) ; return typeof name === "function" ? name ( attributes || { } , children ) : { nodeName : name , attributes : attributes || { } , children : children , key : attributes && attributes . key } ; } export function patch ( velement , element ? ) { var lifecycleCallbacks = [ ] ; element = < Element > patchElement ( element && ( < any > element ) . parentNode , element , element && ( < any > element ) . node == null ? recycleElement ( element , [ ] . map ) : element && ( < any > element ) . node , velement , lifecycleCallbacks , element != null && ( < any > element ) . node == null ) ; element [ "node" ] = velement ; while ( lifecycleCallbacks . length ) lifecycleCallbacks . pop ( ) ! ( ) ; return element ; } function recycleElement ( element , map ) { return { nodeName : element . nodeName . toLowerCase ( ) , attributes : { } , children : map . call ( element . childNodes , function ( element ) { return element . nodeType === 3 ? element . nodeValue : recycleElement ( element , map ) ; } ) } ; } export function merge ( dominant , recessive ) { var obj = { } ; for ( var i in recessive ) obj [ i ] = recessive [ i ] ; for ( var i in dominant ) obj [ i ] = dominant [ i ] ; return obj ; } function getKey ( node ) { return isVElement ( node ) ? node . key : null ; } function updateAttribute ( element , name , value , oldValue , isSVG ) { if ( name === "key" ) return ; if ( typeof value === "function" ) element [ name ] = value ; else if ( value != null && value !== false ) element . setAttribute ( name , value ) ; if ( value == null || value === false ) element . removeAttribute ( name ) ; } function createNode ( vnode , callbacks , isSVG ) { var node = typeof vnode === "string" || typeof vnode === "number" ? document . createTextNode ( "" + vnode ) : ( isSVG = ( isSVG || vnode . nodeName === "svg" ) ) ? document . createElementNS ( "http://www.w3.org/2000/svg" , vnode . nodeName ) : document . createElement ( vnode . nodeName ) ; if ( isVElement ( vnode ) ) { var attributes = vnode . attributes ; if ( attributes ) { if ( attributes . onAttached ) { callbacks . push ( function ( ) { attributes . onAttached ! ( < Element > node , attributes ) ; } ) ; } for ( var i = 0 ; i < vnode . children . length ; i ++ ) { node . appendChild ( createNode ( vnode . children [ i ] , callbacks , isSVG ) ) ; } for ( var name in attributes ) { updateAttribute ( < Element > node , name , attributes [ name ] , null , isSVG ) ; } } } return node ; } function updateElement ( element , oldAttributes , attributes , callbacks , isRecycling , isSVG ) { for ( var name in merge ( attributes , oldAttributes ) ) { if ( attributes [ name ] !== ( name === "value" || name === "checked" ? element [ name ] : oldAttributes [ name ] ) ) { updateAttribute ( element , name , attributes [ name ] , oldAttributes [ name ] , isSVG ) ; } } if ( ! isRecycling && attributes . onBeforeUpdate ) { attributes . onBeforeUpdate ( element , attributes ) ; } if ( ! isRecycling && attributes . onUpdated ) callbacks . push ( ( ) => attributes . onUpdated ! ( element , oldAttributes ) ) ; else if ( isRecycling && attributes . onAttached ) callbacks . push ( ( ) => attributes . onAttached ! ( element , oldAttributes ) ) ; } function removeChildren ( element , node ) { if ( isVElement ( node ) ) { var attributes = node . attributes ; if ( attributes ) { for ( var i = 0 ; i < node . children . length ; i ++ ) { removeChildren ( < Element > element . childNodes [ i ] , node . children [ i ] ) ; } if ( attributes . onRemoved ) { attributes . onRemoved ( element ) ; } } } return element ; } async function removeNode ( parent , element , vnode ) { if ( isVElement ( vnode ) ) { element [ "removing" ] = true ; if ( vnode . attributes . onBeforeRemove ) await vnode . attributes . onBeforeRemove ( < Element > element ) ; removeChildren ( < Element > element , vnode ) ; } parent . removeChild ( element ) ; } function activeNodes ( nodes ) { var activeNodes = [ ] ; for ( var x = 0 ; x < nodes . length ; x ++ ) if ( nodes [ x ] [ "removing" ] !== true ) activeNodes . push ( nodes [ x ] ) ; return activeNodes ; } function patchElement ( parent , node , oldVNode , vnode , lifecycleCallbacks , isRecycling , isSVG = false ) { if ( vnode === oldVNode ) { } else if ( oldVNode == null || oldVNode [ "nodeName" ] !== vnode [ "nodeName" ] ) { var newElement = createNode ( vnode ! , lifecycleCallbacks , isSVG ) ; if ( parent ) { parent . insertBefore ( newElement , node || null ) ; if ( oldVNode != null ) { removeNode ( parent , < Element > node , < VElement > oldVNode ) ; } } node = newElement ; } else if ( oldVNode [ "nodeName" ] == null ) { node ! . nodeValue = "" + vnode ; } else { updateElement ( < Element > node , ( < VElement > oldVNode ) . attributes , ( < VElement > vnode ) . attributes , lifecycleCallbacks , isRecycling , ( isSVG = ( isSVG || ( < VElement > vnode ) . nodeName === "svg" ) ) ) ; var oldKeyed = { } ; var newKeyed = { } ; var oldElements = [ ] ; var oldChildren = ( < VElement > oldVNode ) . children ; var children = ( < VElement > vnode ) . children ; var active = activeNodes ( node ! . childNodes ) ; for ( var i = 0 ; i < oldChildren . length ; i ++ ) { oldElements [ i ] = active [ i ] ; var oldKey = getKey ( oldChildren [ i ] ) ; if ( oldKey != null ) { oldKeyed [ oldKey ] = [ oldElements [ i ] , oldChildren [ i ] ] ; } } var i = 0 ; var k = 0 ; while ( k < children . length ) { var oldKey = getKey ( oldChildren [ i ] ) ; var newKey = getKey ( children [ k ] ) ; if ( oldKey && newKeyed [ oldKey ] ) { i ++ ; continue ; } if ( newKey == null || isRecycling ) { if ( oldKey == null ) { patchElement ( < Element > node , oldElements [ i ] , < VElement > oldChildren [ i ] , < VElement > children [ k ] , lifecycleCallbacks , isRecycling , isSVG ) ; k ++ ; } i ++ ; } else { var keyedNode = oldKeyed [ newKey ] || [ ] ; if ( oldKey === newKey ) { patchElement ( < Element > node , keyedNode [ 0 ] , keyedNode [ 1 ] , < VElement > children [ k ] , lifecycleCallbacks , isRecycling , isSVG ) ; i ++ ; } else if ( keyedNode [ 0 ] ) { patchElement ( < Element > node , node ! . insertBefore ( keyedNode [ 0 ] , oldElements [ i ] ) , keyedNode [ 1 ] , < VElement > children [ k ] , lifecycleCallbacks , isRecycling , isSVG ) ; } else { patchElement ( < Element > node , oldElements [ i ] , undefined , < VElement > children [ k ] , lifecycleCallbacks , isRecycling , isSVG ) ; } newKeyed [ newKey ] = children [ k ] ; k ++ ; } } while ( i < oldChildren . length ) { if ( getKey ( oldChildren [ i ] ) == null ) { removeNode ( < Element > node , < Element > oldElements [ i ] , < VElement > oldChildren [ i ] ) ; } i ++ ; } for ( var j in oldKeyed ) { if ( ! newKeyed [ j ] ) { removeNode ( < Element > node , oldKeyed [ j ] [ 0 ] , oldKeyed [ j ] [ 1 ] ) ; } } } return node ; }