namespace drunk { import util = drunk . util ; import config = drunk . config ; import Promise = drunk . Promise ; export interface IActionExecutor { ( element , ondone ) : ( ) => void ; } export interface IActionDefinition { created : IActionExecutor ; removed : IActionExecutor ; } export interface IActionType { created : string ; removed : string ; } export interface IAction { cancel ? ( ) : void ; promise ? : Promise < any > ; } const weakRefKey = 'drunk:action:binding' ; /**
     * 动画模块
     */ export namespace Action { /**
         * action的类型
         */ export const Type = { created : 'created' , removed : 'removed' } ; /**
         * js动画定义
         */ let definitionMap = { } ; /**
         * 动画状态
         */ let actionMap = { } ; let propertiesMap = { } ; let propertyPrefix = null ; let transitionEndEvent = null ; let animationEndEvent = null ; function getPropertyName ( property ) { if ( propertiesMap [ property ] ) { return propertiesMap [ property ] ; } if ( propertyPrefix === null ) { let style = document . body . style ; if ( 'webkitAnimationDuration' in style ) { propertyPrefix = 'webkit' ; transitionEndEvent = 'webkitTransitionEnd' ; animationEndEvent = 'webkitAnimationEnd' ; } else if ( 'mozAnimationDuration' in style ) { propertyPrefix = 'moz' ; } else if ( 'msAnimationDuration' in style ) { propertyPrefix = 'ms' ; } else { propertyPrefix = '' ; } if ( ! transitionEndEvent && ! animationEndEvent ) { transitionEndEvent = 'transitionend' ; animationEndEvent = 'animationend' ; } } let value = property ; if ( propertyPrefix ) { value = propertyPrefix + ( property . charAt ( 0 ) . toUpperCase ( ) + property . slice ( 1 ) ) ; } propertiesMap [ property ] = value ; return value ; } method param { string } name param { IActionDefinition } definition export function register < T extends IActionDefinition > ( name , definition ) { if ( definitionMap [ name ] != null ) { console . warn ( name , "action的定义已经被覆盖为" , definition ) ; } definitionMap [ name ] = definition ; } param name export function getByName ( name ) { return definitionMap [ name ] ; } param element param action export function setCurrentAction ( element , action ) { let id = util . uniqueId ( element ) ; actionMap [ id ] = action ; } param element export function getCurrentAction ( element ) { let id = util . uniqueId ( element ) ; return actionMap [ id ] ; } param element export function removeRef ( element ) { let id = util . uniqueId ( element ) ; actionMap [ id ] = null ; } param element param detail param type export function run ( element , detail , type ) { if ( isNumber ( detail ) ) { return wait ( < any > detail * 1000 ) ; } if ( definitionMap [ detail ] ) { return runJavascriptAction ( element , detail , type ) ; } return runMaybeCSSAnimation ( element , detail , type ) ; } /**
         * 确认执行元素的所有action
         */ export function process ( target ) ; export function process ( target ) ; export function process ( target ) { let elements ; if ( ! Array . isArray ( target ) ) { elements = [ target ] ; } else { elements = target ; } return Promise . all ( elements . map ( el => { let action = getCurrentAction ( el ) ; return action && action . promise ; } ) ) ; } export function triggerAction ( target , type ) { let action = target [ weakRefKey ] ; if ( action ) { action . runActionByType ( type ) ; return process ( target ) ; } return Promise . resolve ( ) ; } function wait ( time ) { let action = { } ; action . promise = new Promise ( ( resolve , reject ) => { let timerid ; action . cancel = ( ) => { clearTimeout ( timerid ) ; action . cancel = null ; action . promise = null ; reject ( ) ; } ; timerid = setTimeout ( resolve , time ) ; } ) ; return action ; } function runJavascriptAction ( element , detail , type ) { let definition = definitionMap [ detail ] ; let action = { } ; let executor = definition [ type ] ; action . promise = new Promise ( ( resolve , reject ) => { let cancel = executor ( element , ( ) => { resolve ( ) ; } ) ; console . assert ( typeof cancel === 'function' , `drunk.Action: ${ detail }的${ type }状态未提供cancel回调函数` ) ; action . cancel = ( ) => { action . cancel = null ; action . promise = null ; cancel ( ) ; reject ( ) ; } ; } ) ; return action ; } function runMaybeCSSAnimation ( element , detail , type ) { detail = detail ? detail + '-' : config . prefix ; let action = { } ; let className = detail + type ; let style = getComputedStyle ( element , null ) ; let transitionDuration = style [ getPropertyName ( 'transitionDuration' ) ] ; let transitionExist = transitionDuration !== '0s' ; let transitionTimerid ; action . promise = new Promise ( ( resolve , reject ) => { element . classList . add ( className ) ; let animationExist = style [ getPropertyName ( 'animationDuration' ) ] !== '0s' ; if ( ! transitionExist && ! animationExist ) { element . classList . remove ( className ) ; return resolve ( ) ; } element . style [ getPropertyName ( 'animationFillMode' ) ] = 'both' ; function onTransitionEnd ( ) { clearTimeout ( transitionTimerid ) ; element . removeEventListener ( animationEndEvent , onAnimationEnd , false ) ; element . removeEventListener ( transitionEndEvent , onTransitionEnd , false ) ; if ( type === Type . removed ) { element . classList . remove ( detail + Type . created , className ) ; } resolve ( ) ; } function onAnimationEnd ( ) { element . removeEventListener ( transitionEndEvent , onTransitionEnd , false ) ; element . removeEventListener ( animationEndEvent , onAnimationEnd , false ) ; resolve ( ) ; } element . addEventListener ( animationEndEvent , onAnimationEnd , false ) ; element . addEventListener ( transitionEndEvent , onTransitionEnd , false ) ; if ( transitionExist ) { transitionTimerid = setTimeout ( onTransitionEnd , parseFloat ( transitionDuration ) * 1000 ) ; } action . cancel = ( ) => { clearTimeout ( transitionTimerid ) ; element . removeEventListener ( transitionEndEvent , onTransitionEnd , false ) ; element . removeEventListener ( animationEndEvent , onAnimationEnd , false ) ; element . classList . remove ( className ) ; action . cancel = null ; action . promise = null ; reject ( ) ; } ; } ) ; return action ; } } /**
     * action绑定的实现
     */ @ binding ( "action" ) export class ActionBinding extends Binding implements IBindingDefinition { static priority = Binding . Priority . aboveNormal ; private _actionNames : string [ ] ; private _actionJob : util . IAsyncJob ; private _currType : string ; init ( ) { this . element [ weakRefKey ] = this ; this . _actionJob = util . execAsyncWork ( ( ) => { this . _actionJob = null ; if ( document . body && document . body . contains ( this . element ) ) { this . runActionByType ( Action . Type . created ) ; } } ) ; } /**
         * 解析action的定义表达式
         */ private _parseDefinition ( actionType ) { if ( ! this . expression ) { this . _actionNames = [ ] ; } else { let str = this . viewModel . $eval ( this . expression , true ) ; this . _actionNames = str . split ( /\s+/ ) ; if ( actionType === Action . Type . removed ) { this . _actionNames . reverse ( ) ; } } let actions = this . _actionNames ; while ( isNumber ( actions [ actions . length - 1 ] ) ) { actions . pop ( ) ; } } /**
         * 根据类型运行数据的action队列
         */ private _runActions ( type ) { let element = this . element ; if ( this . _actionNames . length < 2 ) { let action = Action . run ( element , this . _actionNames [ 0 ] , type ) ; action . promise . then ( ( ) => { Action . removeRef ( element ) ; } ) ; return Action . setCurrentAction ( element , action ) ; } let actionQueue = { } ; let actions = this . _actionNames ; actionQueue . promise = new Promise ( ( resolve , reject ) => { let index = 0 ; let runAction = ( ) => { let detail = actions [ index ++ ] ; if ( typeof detail === 'undefined' ) { actionQueue . cancel = null ; actionQueue . promise = null ; Action . removeRef ( element ) ; return resolve ( ) ; } let currentAction = Action . run ( element , detail , type ) ; currentAction . promise . then ( runAction ) ; actionQueue . cancel = ( ) => { currentAction . cancel ( ) ; actionQueue . cancel = null ; actionQueue . promise = null ; reject ( ) ; } ; } ; runAction ( ) ; } ) ; Action . setCurrentAction ( element , actionQueue ) ; } /**
         * 先取消还在运行的action，再运行指定的action
         */ runActionByType ( type ) { if ( type === this . _currType ) { return ; } if ( this . _actionJob ) { this . _actionJob . cancel ( ) ; this . _actionJob = null ; } let currentAction = Action . getCurrentAction ( this . element ) ; if ( currentAction && currentAction . cancel ) { currentAction . cancel ( ) ; } this . _parseDefinition ( type ) ; this . _runActions ( type ) ; } release ( ) { if ( this . _actionJob ) { this . _actionJob . cancel ( ) ; } this . runActionByType ( Action . Type . removed ) ; this . element [ weakRefKey ] = null ; this . _actionNames = null ; this . _actionJob = null ; } } function isNumber ( value ) { return ! isNaN ( parseFloat ( value ) ) ; } }