import { EventEmitter , NgZone } from '@angular/core' ; import { getCss , isTextInput } from '../util/dom' ; import { QueryParams } from './query-params' ; import { removeArrayItem } from '../util/util' ; export type DocumentDirection = 'ltr' | 'rtl' ; name description usage Component demo export class Platform { private _win : Window ; private _doc : HTMLDocument ; private _versions : { [ name ] : PlatformVersion ; } = { } ; private _dir : DocumentDirection ; private _lang : string ; private _ua : string ; private _qp = new QueryParams ( ) ; private _nPlt : string ; private _readyPromise : Promise < any > ; private _readyResolve : any ; private _bbActions : BackButtonAction [ ] = [ ] ; private _registry : { [ name ] : PlatformConfig ; } ; private _default : string ; private _pW = 0 ; private _pH = 0 ; private _lW = 0 ; private _lH = 0 ; private _isPortrait : boolean = null ; private _uiEvtOpts = false ; hidden zone : NgZone ; internal Css : { transform ? : string ; transition ? : string ; transitionDuration ? : string ; transitionDelay ? : string ; transitionTimingFn ? : string ; transitionStart ? : string ; transitionEnd ? : string ; transformOrigin ? : string ; animationDelay ? : string ; } ; internal _platforms : string [ ] = [ ] ; constructor ( ) { this . _readyPromise = new Promise ( res => { this . _readyResolve = res ; } ) ; this . backButton . subscribe ( ( ) => { console . debug ( 'hardware back button' ) ; this . runBackButtonAction ( ) ; } ) ; } hidden setWindow ( win ) { this . _win = win ; } hidden win ( ) { return this . _win ; } hidden setDocument ( doc ) { this . _doc = doc ; } hidden doc ( ) { return this . _doc ; } hidden setZone ( zone ) { this . zone = zone ; } hidden setCssProps ( docElement ) { this . Css = getCss ( docElement ) ; } returns { boolean } description Component param { string } platformName is ( platformName ) { return ( this . _platforms . indexOf ( platformName ) > - 1 ) ; } returns { array } description Component platforms ( ) { return this . _platforms ; } Component returns { object } versions ( ) { return this . _versions ; } hidden version ( ) { for ( var platformName in this . _versions ) { if ( this . _versions [ platformName ] ) { return this . _versions [ platformName ] ; } } return { } ; } Component returns { promise } ready ( ) { return this . _readyPromise ; } hidden triggerReady ( readySource ) { this . zone . run ( ( ) => { this . _readyResolve ( readySource ) ; } ) ; } hidden prepareReady ( ) { const self = this ; if ( self . _doc . readyState === 'complete' || self . _doc . readyState === 'interactive' ) { self . triggerReady ( 'dom' ) ; } else { self . _doc . addEventListener ( 'DOMContentLoaded' , completed , false ) ; self . _win . addEventListener ( 'load' , completed , false ) ; } function completed ( ) { self . _doc . removeEventListener ( 'DOMContentLoaded' , completed , false ) ; self . _win . removeEventListener ( 'load' , completed , false ) ; self . triggerReady ( 'dom' ) ; } } param { DocumentDirection } dir param { boolean } updateDocument setDir ( dir , updateDocument ) { this . _dir = dir ; this . isRTL = ( dir === 'rtl' ) ; if ( updateDocument !== false ) { this . _doc [ 'documentElement' ] . setAttribute ( 'dir' , dir ) ; } } returns { DocumentDirection } dir ( ) { return this . _dir ; } returns { boolean } isRTL : boolean ; param { string } language param { boolean } updateDocument setLang ( language , updateDocument ) { this . _lang = language ; if ( updateDocument !== false ) { this . _doc [ 'documentElement' ] . setAttribute ( 'lang' , language ) ; } } returns { string } lang ( ) { return this . _lang ; } hidden exitApp ( ) { } hidden backButton : EventEmitter < Event > = new EventEmitter < Event > ( ) ; /**
     * The pause event emits when the native platform puts the application
     * into the background, typically when the user switches to a different
     * application. This event would emit when a Cordova app is put into
     * the background, however, it would not fire on a standard web browser.
     */ pause : EventEmitter < Event > = new EventEmitter < Event > ( ) ; /**
     * The resume event emits when the native platform pulls the application
     * out from the background. This event would emit when a Cordova app comes
     * out from the background, however, it would not fire on a standard web browser.
     */ resume : EventEmitter < Event > = new EventEmitter < Event > ( ) ; /**
     * The resize event emits when the browser window has changed dimensions. This
     * could be from a browser window being physically resized, or from a device
     * changing orientation.
     */ resize : EventEmitter < Event > = new EventEmitter < Event > ( ) ; param { Function } fn param { number } priority returns { Function } registerBackButtonAction ( fn , priority = 0 ) { const action = { fn , priority } ; this . _bbActions . push ( action ) ; return ( ) => { removeArrayItem ( this . _bbActions , action ) ; } ; } hidden runBackButtonAction ( ) { let winner = null ; this . _bbActions . forEach ( ( action ) => { if ( ! winner || action . priority >= winner . priority ) { winner = action ; } } ) ; winner && winner . fn && winner . fn ( ) ; } hidden setUserAgent ( userAgent ) { this . _ua = userAgent ; } hidden setQueryParams ( url ) { this . _qp . parseUrl ( url ) ; } /**
     * Get the query string parameter
     */ getQueryParam ( key ) { return this . _qp . get ( key ) ; } /**
     * Get the current url.
     */ url ( ) { return this . _win [ 'location' ] [ 'href' ] ; } hidden userAgent ( ) { return this . _ua || '' ; } hidden setNavigatorPlatform ( navigatorPlt ) { this . _nPlt = navigatorPlt ; } hidden navigatorPlatform ( ) { return this . _nPlt || '' ; } /**
     * Gets the width of the platform's viewport using `window.innerWidth`.
     * Using this method is preferred since the dimension is a cached value,
     * which reduces the chance of multiple and expensive DOM reads.
     */ width ( ) { this . _calcDim ( ) ; return this . _isPortrait ? this . _pW : this . _lW ; } /**
     * Gets the height of the platform's viewport using `window.innerHeight`.
     * Using this method is preferred since the dimension is a cached value,
     * which reduces the chance of multiple and expensive DOM reads.
     */ height ( ) { this . _calcDim ( ) ; return this . _isPortrait ? this . _pH : this . _lH ; } hidden getElementComputedStyle ( ele , pseudoEle ? ) { return this . _win [ 'getComputedStyle' ] ( ele , pseudoEle ) ; } hidden getElementFromPoint ( x , y ) { return < HTMLElement > this . _doc [ 'elementFromPoint' ] ( x , y ) ; } hidden getElementBoundingClientRect ( ele ) { return ele [ 'getBoundingClientRect' ] ( ) ; } /**
     * Returns `true` if the app is in portait mode.
     */ isPortrait ( ) { this . _calcDim ( ) ; return this . _isPortrait ; } /**
     * Returns `true` if the app is in landscape mode.
     */ isLandscape ( ) { return ! this . isPortrait ( ) ; } private _calcDim ( ) { if ( this . _isPortrait === null || this . _isPortrait === false && this . _win [ 'innerWidth' ] < this . _win [ 'innerHeight' ] ) { var win = this . _win ; var innerWidth = win [ 'innerWidth' ] ; var innerHeight = win [ 'innerHeight' ] ; if ( win . screen . width > 0 && win . screen . height > 0 ) { if ( innerWidth < innerHeight ) { if ( this . _pW <= innerWidth ) { console . debug ( 'setting _isPortrait to true' ) ; this . _isPortrait = true ; this . _pW = innerWidth ; } if ( this . _pH <= innerHeight ) { console . debug ( 'setting _isPortrait to true' ) ; this . _isPortrait = true ; this . _pH = innerHeight ; } } else { if ( this . _lW !== innerWidth ) { console . debug ( 'setting _isPortrait to false' ) ; this . _isPortrait = false ; this . _lW = innerWidth ; } if ( this . _lH !== innerHeight ) { console . debug ( 'setting _isPortrait to false' ) ; this . _isPortrait = false ; this . _lH = innerHeight ; } } } } } hidden raf ( callback ) { const win = this . _win ; return win [ '__zone_symbol__requestAnimationFrame' ] ( callback ) ; } hidden cancelRaf ( rafId ) { const win = this . _win ; return win [ '__zone_symbol__cancelAnimationFrame' ] ( rafId ) ; } hidden timeout ( callback , timeout ? ) { const win = this . _win ; return win [ '__zone_symbol__setTimeout' ] ( callback , timeout ) ; } hidden cancelTimeout ( timeoutId ) { const win = this . _win ; win [ '__zone_symbol__clearTimeout' ] ( timeoutId ) ; } hidden registerListener ( ele , eventName , callback , opts , unregisterListenersCollection ? ) { const listenerOpts = this . _uiEvtOpts ? { 'capture' : ! ! opts . capture , 'passive' : ! ! opts . passive , } : ! ! opts . capture ; let unReg ; if ( ! opts . zone && ele [ '__zone_symbol__addEventListener' ] ) { ele [ '__zone_symbol__addEventListener' ] ( eventName , callback , listenerOpts ) ; unReg = function unregisterListener ( ) { ele [ '__zone_symbol__removeEventListener' ] ( eventName , callback , listenerOpts ) ; } ; } else { ele [ 'addEventListener' ] ( eventName , callback , listenerOpts ) ; unReg = function unregisterListener ( ) { ele [ 'removeEventListener' ] ( eventName , callback , listenerOpts ) ; } ; } if ( unregisterListenersCollection ) { unregisterListenersCollection . push ( unReg ) ; } return unReg ; } hidden transitionEnd ( el , callback , zone = true ) { const unRegs = [ ] ; function unregister ( ) { unRegs . forEach ( unReg => { unReg ( ) ; } ) ; } function onTransitionEnd ( ev ) { if ( el === ev . target ) { unregister ( ) ; callback ( ev ) ; } } if ( el ) { this . registerListener ( el , 'webkitTransitionEnd' , < any > onTransitionEnd , { zone : zone } , unRegs ) ; this . registerListener ( el , 'transitionend' , < any > onTransitionEnd , { zone : zone } , unRegs ) ; } return unregister ; } hidden windowLoad ( callback ) { const win = this . _win ; const doc = this . _doc ; let unreg ; if ( doc . readyState === 'complete' ) { callback ( win , doc ) ; } else { unreg = this . registerListener ( win , 'load' , ( ) => { unreg && unreg ( ) ; callback ( win , doc ) ; } , { zone : false } ) ; } } hidden isActiveElement ( ele ) { return ! ! ( ele && ( this . getActiveElement ( ) === ele ) ) ; } hidden getActiveElement ( ) { return this . _doc [ 'activeElement' ] ; } hidden hasFocus ( ele ) { return ! ! ( ( ele && ( this . getActiveElement ( ) === ele ) ) && ( ele . parentElement . querySelector ( ':focus' ) === ele ) ) ; } hidden hasFocusedTextInput ( ) { const ele = this . getActiveElement ( ) ; if ( isTextInput ( ele ) ) { return ( ele . parentElement . querySelector ( ':focus' ) === ele ) ; } return false ; } hidden focusOutActiveElement ( ) { const activeElement = this . getActiveElement ( ) ; activeElement && activeElement . blur && activeElement . blur ( ) ; } private _initEvents ( ) { try { var opts = Object . defineProperty ( { } , 'passive' , { get : ( ) => { this . _uiEvtOpts = true ; } } ) ; this . _win . addEventListener ( 'optsTest' , null , opts ) ; } catch ( e ) { } this . timeout ( ( ) => { var timerId ; this . registerListener ( this . _win , 'resize' , ( ) => { clearTimeout ( timerId ) ; timerId = setTimeout ( ( ) => { if ( this . hasFocusedTextInput ( ) === false ) { this . _isPortrait = null ; } this . zone . run ( ( ) => this . resize . emit ( ) ) ; } , 200 ) ; } , { passive : true , zone : false } ) ; } , 2000 ) ; } hidden setPlatformConfigs ( platformConfigs ) { this . _registry = platformConfigs || { } ; } hidden getPlatformConfig ( platformName ) { return this . _registry [ platformName ] || { } ; } hidden registry ( ) { return this . _registry ; } hidden setDefault ( platformName ) { this . _default = platformName ; } hidden testQuery ( queryValue , queryTestValue ) { const valueSplit = queryValue . toLowerCase ( ) . split ( ';' ) ; return valueSplit . indexOf ( queryTestValue ) > - 1 ; } hidden testNavigatorPlatform ( navigatorPlatformExpression ) { const rgx = new RegExp ( navigatorPlatformExpression , 'i' ) ; return rgx . test ( this . _nPlt ) ; } hidden matchUserAgentVersion ( userAgentExpression ) { if ( this . _ua && userAgentExpression ) { const val = this . _ua . match ( userAgentExpression ) ; if ( val ) { return { major : val [ 1 ] , minor : val [ 2 ] } ; } } } testUserAgent ( expression ) { if ( this . _ua ) { return this . _ua . indexOf ( expression ) >= 0 ; } return false ; } hidden isPlatformMatch ( queryStringName , userAgentAtLeastHas ? , userAgentMustNotHave = [ ] ) { const queryValue = this . _qp . get ( 'ionicplatform' ) ; if ( queryValue ) { return this . testQuery ( queryValue , queryStringName ) ; } userAgentAtLeastHas = userAgentAtLeastHas || [ queryStringName ] ; const userAgent = this . _ua . toLowerCase ( ) ; for ( var i = 0 ; i < userAgentAtLeastHas . length ; i ++ ) { if ( userAgent . indexOf ( userAgentAtLeastHas [ i ] ) > - 1 ) { for ( var j = 0 ; j < userAgentMustNotHave . length ; j ++ ) { if ( userAgent . indexOf ( userAgentMustNotHave [ j ] ) > - 1 ) { return false ; } } return true ; } } return false ; } hidden init ( ) { this . _initEvents ( ) ; let rootPlatformNode ; let enginePlatformNode ; let tmpPlt ; for ( let platformName in this . _registry ) { tmpPlt = this . matchPlatform ( platformName ) ; if ( tmpPlt ) { if ( tmpPlt . isEngine ) { enginePlatformNode = tmpPlt ; } else if ( ! rootPlatformNode || tmpPlt . depth > rootPlatformNode . depth ) { rootPlatformNode = tmpPlt ; } } } if ( ! rootPlatformNode ) { rootPlatformNode = new PlatformNode ( this . _registry , this . _default ) ; } if ( rootPlatformNode ) { if ( enginePlatformNode ) { enginePlatformNode . child = rootPlatformNode ; rootPlatformNode . parent = enginePlatformNode ; rootPlatformNode = enginePlatformNode ; } let platformNode = rootPlatformNode ; while ( platformNode ) { insertSuperset ( this . _registry , platformNode ) ; platformNode = platformNode . child ; } platformNode = rootPlatformNode . parent ; while ( platformNode ) { rootPlatformNode = platformNode ; platformNode = platformNode . parent ; } platformNode = rootPlatformNode ; while ( platformNode ) { platformNode . initialize ( this ) ; if ( platformNode . name === 'iphone' && this . navigatorPlatform ( ) === 'iPad' ) { this . _platforms . push ( 'tablet' ) ; this . _platforms . push ( 'ipad' ) ; return ; } this . _platforms . push ( platformNode . name ) ; this . _versions [ platformNode . name ] = platformNode . version ( this ) ; platformNode = platformNode . child ; } } if ( this . _platforms . indexOf ( 'mobile' ) > - 1 && this . _platforms . indexOf ( 'cordova' ) === - 1 ) { this . _platforms . push ( 'mobileweb' ) ; } } hidden private matchPlatform ( platformName ) { let platformNode = new PlatformNode ( this . _registry , platformName ) ; let rootNode = platformNode . getRoot ( this ) ; if ( rootNode ) { rootNode . depth = 0 ; let childPlatform = rootNode . child ; while ( childPlatform ) { rootNode . depth ++ ; childPlatform = childPlatform . child ; } } return rootNode ; } } function insertSuperset ( registry , platformNode ) { let supersetPlaformName = platformNode . superset ( ) ; if ( supersetPlaformName ) { let supersetPlatform = new PlatformNode ( registry , supersetPlaformName ) ; supersetPlatform . parent = platformNode . parent ; supersetPlatform . child = platformNode ; if ( supersetPlatform . parent ) { supersetPlatform . parent . child = supersetPlatform ; } platformNode . parent = supersetPlatform ; } } hidden class PlatformNode { private c : PlatformConfig ; parent : PlatformNode ; child : PlatformNode ; name : string ; isEngine : boolean ; depth : number ; constructor ( public registry , platformName ) { this . c = registry [ platformName ] ; this . name = platformName ; this . isEngine = this . c . isEngine ; } settings ( ) { return this . c . settings || { } ; } superset ( ) { return this . c . superset ; } isMatch ( p ) { return this . c . isMatch && this . c . isMatch ( p ) || false ; } initialize ( plt ) { this . c . initialize && this . c . initialize ( plt ) ; } version ( plt ) { if ( this . c . versionParser ) { const v = this . c . versionParser ( plt ) ; if ( v ) { const str = v . major + '.' + v . minor ; return { str : str , num : parseFloat ( str ) , major : parseInt ( v . major , 10 ) , minor : parseInt ( v . minor , 10 ) } ; } } } getRoot ( plt ) { if ( this . isMatch ( plt ) ) { let parents = this . getSubsetParents ( this . name ) ; if ( ! parents . length ) { return this ; } let platformNode = null ; let rootPlatformNode = null ; for ( let i = 0 ; i < parents . length ; i ++ ) { platformNode = new PlatformNode ( this . registry , parents [ i ] ) ; platformNode . child = this ; rootPlatformNode = platformNode . getRoot ( plt ) ; if ( rootPlatformNode ) { this . parent = platformNode ; return rootPlatformNode ; } } } return null ; } getSubsetParents ( subsetPlatformName ) { const parentPlatformNames = [ ] ; let pltConfig = null ; for ( let platformName in this . registry ) { pltConfig = this . registry [ platformName ] ; if ( pltConfig . subsets && pltConfig . subsets . indexOf ( subsetPlatformName ) > - 1 ) { parentPlatformNames . push ( platformName ) ; } } return parentPlatformNames ; } } export interface PlatformConfig { isEngine ? : boolean ; initialize ? : Function ; isMatch ? : Function ; superset ? : string ; subsets ? : string [ ] ; settings ? : any ; versionParser ? : any ; } export interface PlatformVersion { str ? : string ; num ? : number ; major ? : number ; minor ? : number ; } interface BackButtonAction { fn : Function ; priority : number ; } export interface EventListenerOptions { capture ? : boolean ; passive ? : boolean ; zone ? : boolean ; } hidden export function setupPlatform ( doc , platformConfigs , zone ) { const plt = new Platform ( ) ; plt . setDefault ( 'core' ) ; plt . setPlatformConfigs ( platformConfigs ) ; plt . setZone ( zone ) ; const docElement = doc . documentElement ; plt . setDocument ( doc ) ; const dir = docElement . dir ; plt . setDir ( dir === 'rtl' ? 'rtl' : 'ltr' , ! dir ) ; plt . setLang ( docElement . lang , false ) ; plt . setCssProps ( docElement ) ; const win = doc . defaultView ; plt . setWindow ( win ) ; plt . setNavigatorPlatform ( win . navigator . platform ) ; plt . setUserAgent ( win . navigator . userAgent ) ; plt . setQueryParams ( win . location . href ) ; plt . init ( ) ; ( < any > win ) [ 'Ionic' ] = ( < any > win ) [ 'Ionic' ] || { } ; ( < any > win ) [ 'Ionic' ] [ 'platform' ] = plt ; return plt ; }