2014-09-30 12:29:53 +02:00
/ * !
* Angular Material Design
* https : //github.com/angular/material
* @ license MIT
2014-11-06 16:55:16 +01:00
* v0 . 5.0
2014-09-30 12:29:53 +02:00
* /
2014-11-03 17:32:25 +01:00
( function ( ) {
2014-11-06 16:55:16 +01:00
angular . module ( 'ngMaterial' , [ "ng" , "ngAnimate" , "ngAria" , "material.core" , "material.decorators" , "material.animations" , "material.components.backdrop" , "material.components.bottomSheet" , "material.components.button" , "material.components.card" , "material.components.checkbox" , "material.components.content" , "material.components.dialog" , "material.components.divider" , "material.components.icon" , "material.components.list" , "material.components.progressCircular" , "material.components.progressLinear" , "material.components.radioButton" , "material.components.sidenav" , "material.components.slider" , "material.components.sticky" , "material.components.subheader" , "material.components.swipe" , "material.components.switch" , "material.components.tabs" , "material.components.textField" , "material.components.toast" , "material.components.toolbar" , "material.components.tooltip" , "material.components.whiteframe" , "material.services.aria" , "material.services.attrBind" , "material.services.compiler" , "material.services.interimElement" , "material.services.media" , "material.services.registry" , "material.services.theming" ] ) ; } ) ( ) ;
2014-10-12 19:07:47 +02:00
2014-11-03 17:32:25 +01:00
( function ( ) {
2014-10-12 19:07:47 +02:00
/ * *
2014-11-03 17:32:25 +01:00
* Angular Mds initialization function that validates environment
2014-10-12 19:07:47 +02:00
* requirements .
* /
2014-11-03 17:32:25 +01:00
angular . module ( 'material.core' , [ ] )
2014-10-12 19:07:47 +02:00
. run ( function validateEnvironment ( ) {
2014-11-03 17:32:25 +01:00
if ( typeof Hammer === 'undefined' ) {
2014-10-12 19:07:47 +02:00
throw new Error (
2014-11-03 17:32:25 +01:00
'ngMaterial requires HammerJS to be preloaded.'
2014-10-12 19:07:47 +02:00
) ;
}
} ) ;
2014-11-03 17:32:25 +01:00
} ) ( ) ;
2014-10-12 19:07:47 +02:00
2014-11-03 17:32:25 +01:00
( function ( ) {
angular . module ( 'material.core' )
. constant ( '$mdConstant' , {
KEY _CODE : {
ENTER : 13 ,
ESCAPE : 27 ,
SPACE : 32 ,
LEFT _ARROW : 37 ,
UP _ARROW : 38 ,
RIGHT _ARROW : 39 ,
DOWN _ARROW : 40
}
} ) ;
} ) ( ) ;
2014-09-30 12:29:53 +02:00
2014-11-03 17:32:25 +01:00
( function ( ) {
angular . module ( 'material.core' )
2014-11-06 16:55:16 +01:00
. factory ( '$mdUtil' , [ '$cacheFactory' , function ( $cacheFactory ) {
2014-11-03 17:32:25 +01:00
var SPECIAL _CHARS _REGEXP = /([\:\-\_]+(.))/g ;
/* for nextUid() function below */
var uid = [ '0' , '0' , '0' ] ;
2014-09-30 12:29:53 +02:00
2014-11-03 17:32:25 +01:00
var Util ;
return Util = {
now : window . performance ? angular . bind ( window . performance , window . performance . now ) : Date . now ,
2014-09-30 12:29:53 +02:00
2014-11-03 17:32:25 +01:00
/ * *
* Checks if the specified element has an ancestor ( ancestor being parent , grandparent , etc )
* with the given attribute defined .
*
* Also pass in an optional ` limit ` ( levels of ancestry to scan ) , default 4.
* /
ancestorHasAttribute : function ancestorHasAttribute ( element , attrName , limit ) {
limit = limit || 4 ;
var current = element ;
while ( limit -- && current . length ) {
if ( current [ 0 ] . hasAttribute && current [ 0 ] . hasAttribute ( attrName ) ) {
return true ;
}
current = current . parent ( ) ;
}
return false ;
} ,
2014-09-30 12:29:53 +02:00
2014-11-03 17:32:25 +01:00
/ * *
* Checks to see if the element or its parents are disabled .
* @ param element DOM element to start scanning for ` disabled ` attribute
* @ param limit Number of parent levels that should be scanned ; defaults to 4
* @ returns { * } Boolean
* /
isParentDisabled : function isParentDisabled ( element , limit ) {
return Util . ancestorHasAttribute ( element , 'disabled' , limit ) ;
} ,
2014-09-30 12:29:53 +02:00
2014-11-03 17:32:25 +01:00
/ * *
* Checks if two elements have the same parent
* /
elementIsSibling : function elementIsSibling ( element , otherElement ) {
return element . parent ( ) . length &&
( element . parent ( ) [ 0 ] === otherElement . parent ( ) [ 0 ] ) ;
} ,
2014-09-30 12:29:53 +02:00
2014-11-03 17:32:25 +01:00
/ * *
* Converts snake _case to camelCase .
* @ param name Name to normalize
* /
camelCase : function camelCase ( name ) {
return name
. replace ( SPECIAL _CHARS _REGEXP , function ( _ , separator , letter , offset ) {
return offset ? letter . toUpperCase ( ) : letter ;
} ) ;
} ,
2014-09-30 12:29:53 +02:00
2014-11-03 17:32:25 +01:00
/ * *
* Selects 'n' words from a string
* for use in an HTML attribute
* /
stringFromTextBody : function stringFromTextBody ( textBody , numWords ) {
var string = textBody . trim ( ) ;
2014-09-30 12:29:53 +02:00
2014-11-03 17:32:25 +01:00
if ( string . split ( /\s+/ ) . length > numWords ) {
string = textBody . split ( /\s+/ ) . slice ( 1 , ( numWords + 1 ) ) . join ( " " ) + '...' ;
}
return string ;
} ,
2014-09-30 12:29:53 +02:00
2014-11-03 17:32:25 +01:00
/ * *
* Publish the iterator facade to easily support iteration and accessors
* @ see iterator below
* /
iterator : iterator ,
2014-11-06 16:55:16 +01:00
/ * *
* @ see cacheFactory below
* /
cacheFactory : cacheFactory ,
2014-11-03 17:32:25 +01:00
// Returns a function, that, as long as it continues to be invoked, will not
// be triggered. The function will be called after it stops being called for
// N milliseconds. If `immediate` is passed, trigger the function on the
// leading edge, instead of the trailing.
debounce : function debounce ( func , wait , immediate ) {
var timeout ;
return function debounced ( ) {
var context = this , args = arguments ;
clearTimeout ( timeout ) ;
timeout = setTimeout ( function ( ) {
timeout = null ;
if ( ! immediate ) func . apply ( context , args ) ;
} , wait ) ;
if ( immediate && ! timeout ) func . apply ( context , args ) ;
} ;
} ,
2014-09-30 12:29:53 +02:00
2014-11-03 17:32:25 +01:00
// Returns a function that can only be triggered every `delay` milliseconds.
// In other words, the function will not be called unless it has been more
// than `delay` milliseconds since the last call.
throttle : function throttle ( func , delay ) {
var recent ;
return function throttled ( ) {
var context = this ;
var args = arguments ;
var now = Util . now ( ) ;
if ( ! recent || recent - now > delay ) {
func . apply ( context , args ) ;
recent = now ;
}
} ;
} ,
2014-09-30 12:29:53 +02:00
2014-11-03 17:32:25 +01:00
/ * *
* Wraps an element with a tag
*
* @ param el element to wrap
* @ param tag tag to wrap it with
* @ param [ className ] optional class to apply to the wrapper
* @ returns new element
*
* /
wrap : function ( el , tag , className ) {
if ( el . hasOwnProperty ( 0 ) ) { el = el [ 0 ] ; }
var wrapper = document . createElement ( tag ) ;
wrapper . className += className ;
wrapper . appendChild ( el . parentNode . replaceChild ( wrapper , el ) ) ;
return angular . element ( wrapper ) ;
} ,
2014-09-30 12:29:53 +02:00
2014-11-03 17:32:25 +01:00
/ * *
* nextUid , from angular . js .
* A consistent way of creating unique IDs in angular . The ID is a sequence of alpha numeric
* characters such as '012ABC' . The reason why we are not using simply a number counter is that
* the number string gets longer over time , and it can also overflow , where as the nextId
* will grow much slower , it is a string , and it will never overflow .
*
* @ returns an unique alpha - numeric string
* /
nextUid : function ( ) {
var index = uid . length ;
var digit ;
while ( index ) {
index -- ;
digit = uid [ index ] . charCodeAt ( 0 ) ;
if ( digit == 57 /*'9'*/ ) {
uid [ index ] = 'A' ;
return uid . join ( '' ) ;
}
if ( digit == 90 /*'Z'*/ ) {
uid [ index ] = '0' ;
} else {
uid [ index ] = String . fromCharCode ( digit + 1 ) ;
return uid . join ( '' ) ;
}
}
uid . unshift ( '0' ) ;
return uid . join ( '' ) ;
} ,
2014-09-30 12:29:53 +02:00
2014-11-03 17:32:25 +01:00
// Stop watchers and events from firing on a scope without destroying it,
// by disconnecting it from its parent and its siblings' linked lists.
disconnectScope : function disconnectScope ( scope ) {
if ( ! scope ) return ;
2014-09-30 12:29:53 +02:00
2014-11-03 17:32:25 +01:00
// we can't destroy the root scope or a scope that has been already destroyed
if ( scope . $root === scope ) return ;
if ( scope . $$destroyed ) return ;
2014-09-30 12:29:53 +02:00
2014-11-03 17:32:25 +01:00
var parent = scope . $parent ;
scope . $$disconnected = true ;
2014-09-30 12:29:53 +02:00
2014-11-03 17:32:25 +01:00
// See Scope.$destroy
if ( parent . $$childHead === scope ) parent . $$childHead = scope . $$nextSibling ;
if ( parent . $$childTail === scope ) parent . $$childTail = scope . $$prevSibling ;
if ( scope . $$prevSibling ) scope . $$prevSibling . $$nextSibling = scope . $$nextSibling ;
if ( scope . $$nextSibling ) scope . $$nextSibling . $$prevSibling = scope . $$prevSibling ;
2014-09-30 12:29:53 +02:00
2014-11-03 17:32:25 +01:00
scope . $$nextSibling = scope . $$prevSibling = null ;
2014-09-30 12:29:53 +02:00
2014-11-03 17:32:25 +01:00
} ,
2014-09-30 12:29:53 +02:00
2014-11-03 17:32:25 +01:00
// Undo the effects of disconnectScope above.
reconnectScope : function reconnectScope ( scope ) {
if ( ! scope ) return ;
2014-09-30 12:29:53 +02:00
2014-11-03 17:32:25 +01:00
// we can't disconnect the root node or scope already disconnected
if ( scope . $root === scope ) return ;
if ( ! scope . $$disconnected ) return ;
2014-09-30 12:29:53 +02:00
2014-11-03 17:32:25 +01:00
var child = scope ;
2014-09-30 12:29:53 +02:00
2014-11-03 17:32:25 +01:00
var parent = child . $parent ;
child . $$disconnected = false ;
// See Scope.$new for this logic...
child . $$prevSibling = parent . $$childTail ;
if ( parent . $$childHead ) {
parent . $$childTail . $$nextSibling = child ;
parent . $$childTail = child ;
} else {
parent . $$childHead = parent . $$childTail = child ;
}
}
} ;
2014-09-30 12:29:53 +02:00
2014-10-12 19:07:47 +02:00
/ *
2014-11-03 17:32:25 +01:00
* iterator is a list facade to easily support iteration and accessors
*
* @ param items Array list which this iterator will enumerate
* @ param reloop Boolean enables iterator to consider the list as an endless reloop
2014-10-12 19:07:47 +02:00
* /
2014-11-03 17:32:25 +01:00
function iterator ( items , reloop ) {
var trueFn = function ( ) { return true ; } ;
2014-09-30 12:29:53 +02:00
2014-11-03 17:32:25 +01:00
reloop = ! ! reloop ;
var _items = items || [ ] ;
2014-09-30 12:29:53 +02:00
2014-11-03 17:32:25 +01:00
// Published API
return {
items : getItems ,
count : count ,
2014-09-30 12:29:53 +02:00
2014-11-03 17:32:25 +01:00
inRange : inRange ,
contains : contains ,
indexOf : indexOf ,
itemAt : itemAt ,
2014-09-30 12:29:53 +02:00
2014-11-03 17:32:25 +01:00
findBy : findBy ,
2014-09-30 12:29:53 +02:00
2014-11-03 17:32:25 +01:00
add : add ,
remove : remove ,
2014-09-30 12:29:53 +02:00
2014-11-03 17:32:25 +01:00
first : first ,
last : last ,
next : next ,
previous : previous ,
2014-09-30 12:29:53 +02:00
2014-11-03 17:32:25 +01:00
hasPrevious : hasPrevious ,
hasNext : hasNext
2014-09-30 12:29:53 +02:00
2014-11-03 17:32:25 +01:00
} ;
2014-09-30 12:29:53 +02:00
2014-11-03 17:32:25 +01:00
/ *
* Publish copy of the enumerable set
* @ returns { Array | * }
* /
function getItems ( ) {
return [ ] . concat ( _items ) ;
}
2014-09-30 12:29:53 +02:00
2014-11-03 17:32:25 +01:00
/ *
* Determine length of the list
* @ returns { Array . length | * | number }
* /
function count ( ) {
return _items . length ;
}
2014-09-30 12:29:53 +02:00
2014-11-03 17:32:25 +01:00
/ *
* Is the index specified valid
* @ param index
* @ returns { Array . length | * | number | boolean }
* /
function inRange ( index ) {
return _items . length && ( index > - 1 ) && ( index < _items . length ) ;
}
2014-09-30 12:29:53 +02:00
2014-11-03 17:32:25 +01:00
/ *
* Can the iterator proceed to the next item in the list ; relative to
* the specified item .
*
* @ param item
* @ returns { Array . length | * | number | boolean }
* /
function hasNext ( item ) {
return item ? inRange ( indexOf ( item ) + 1 ) : false ;
}
2014-10-12 19:07:47 +02:00
2014-11-03 17:32:25 +01:00
/ *
* Can the iterator proceed to the previous item in the list ; relative to
* the specified item .
*
* @ param item
* @ returns { Array . length | * | number | boolean }
* /
function hasPrevious ( item ) {
return item ? inRange ( indexOf ( item ) - 1 ) : false ;
}
2014-10-12 19:07:47 +02:00
2014-11-03 17:32:25 +01:00
/ *
* Get item at specified index / position
* @ param index
* @ returns { * }
* /
function itemAt ( index ) {
return inRange ( index ) ? _items [ index ] : null ;
2014-09-30 12:29:53 +02:00
}
2014-11-03 17:32:25 +01:00
/ *
* Find all elements matching the key / value pair
* otherwise return null
*
* @ param val
* @ param key
*
* @ return array
* /
function findBy ( key , val ) {
return _items . filter ( function ( item ) {
return item [ key ] === val ;
} ) ;
}
2014-10-12 19:07:47 +02:00
2014-11-03 17:32:25 +01:00
/ *
* Add item to list
* @ param item
* @ param index
* @ returns { * }
* /
function add ( item , index ) {
if ( ! item ) return - 1 ;
2014-09-30 12:29:53 +02:00
2014-11-03 17:32:25 +01:00
if ( ! angular . isNumber ( index ) ) {
index = _items . length ;
}
2014-10-12 19:07:47 +02:00
2014-11-03 17:32:25 +01:00
_items . splice ( index , 0 , item ) ;
2014-09-30 12:29:53 +02:00
2014-11-03 17:32:25 +01:00
return indexOf ( item ) ;
2014-09-30 12:29:53 +02:00
}
2014-11-03 17:32:25 +01:00
/ *
* Remove item from list ...
* @ param item
* /
function remove ( item ) {
if ( contains ( item ) ) {
_items . splice ( indexOf ( item ) , 1 ) ;
2014-10-12 19:07:47 +02:00
}
2014-11-03 17:32:25 +01:00
}
2014-10-12 19:07:47 +02:00
2014-11-03 17:32:25 +01:00
/ *
* Get the zero - based index of the target item
* @ param item
* @ returns { * }
* /
function indexOf ( item ) {
return _items . indexOf ( item ) ;
}
2014-10-12 19:07:47 +02:00
2014-11-03 17:32:25 +01:00
/ *
* Boolean existence check
* @ param item
* @ returns { boolean }
* /
function contains ( item ) {
return item && ( indexOf ( item ) > - 1 ) ;
2014-10-12 19:07:47 +02:00
}
2014-11-03 17:32:25 +01:00
/ *
* Find the next item . If reloop is true and at the end of the list , it will
* go back to the first item . If given , the ` validate ` callback will be used
* determine whether the next item is valid . If not valid , it will try to find the
* next item again .
* @ param item
* @ param { optional } validate
* @ returns { * }
* /
function next ( item , validate ) {
validate = validate || trueFn ;
2014-10-12 19:07:47 +02:00
2014-11-03 17:32:25 +01:00
if ( contains ( item ) ) {
var index = indexOf ( item ) + 1 ,
found = inRange ( index ) ? _items [ index ] : ( reloop ? first ( ) : null ) ;
2014-10-12 19:07:47 +02:00
2014-11-03 17:32:25 +01:00
return validate ( found ) ? found : next ( found , validate ) ;
}
2014-10-12 19:07:47 +02:00
2014-11-03 17:32:25 +01:00
return null ;
}
2014-10-12 19:07:47 +02:00
2014-11-03 17:32:25 +01:00
/ *
* Find the previous item . If reloop is true and at the beginning of the list , it will
* go back to the last item . If given , the ` validate ` callback will be used
* determine whether the previous item is valid . If not valid , it will try to find the
* previous item again .
* @ param item
* @ param { optional } validate
* @ returns { * }
* /
function previous ( item , validate ) {
validate = validate || trueFn ;
2014-10-12 19:07:47 +02:00
2014-11-03 17:32:25 +01:00
if ( contains ( item ) ) {
var index = indexOf ( item ) - 1 ,
found = inRange ( index ) ? _items [ index ] : ( reloop ? last ( ) : null ) ;
2014-10-12 19:07:47 +02:00
2014-11-03 17:32:25 +01:00
return validate ( found ) ? found : previous ( found , validate ) ;
}
2014-10-12 19:07:47 +02:00
2014-11-03 17:32:25 +01:00
return null ;
}
2014-10-12 19:07:47 +02:00
2014-11-03 17:32:25 +01:00
/ *
* Return first item in the list
* @ returns { * }
* /
function first ( ) {
return _items . length ? _items [ 0 ] : null ;
}
2014-10-12 19:07:47 +02:00
2014-11-03 17:32:25 +01:00
/ *
* Return last item in the list ...
* @ returns { * }
* /
function last ( ) {
return _items . length ? _items [ _items . length - 1 ] : null ;
2014-10-12 19:07:47 +02:00
}
2014-09-30 12:29:53 +02:00
}
2014-11-06 16:55:16 +01:00
/ *
* Angular 's $cacheFactory doesn' t have a keys ( ) method ,
* so we add one ourself .
* /
function cacheFactory ( id , options ) {
var cache = $cacheFactory ( id , options ) ;
var keys = { } ;
cache . _put = cache . put ;
cache . put = function ( k , v ) {
keys [ k ] = true ;
return cache . _put ( k , v ) ;
} ;
cache . _remove = cache . remove ;
cache . remove = function ( k ) {
delete keys [ k ] ;
return cache . _remove ( k ) ;
} ;
cache . keys = function ( ) {
return Object . keys ( keys ) ;
} ;
return cache ;
}
} ] ) ;
2014-09-30 12:29:53 +02:00
/ *
* Since removing jQuery from the demos , some code that uses ` element.focus() ` is broken .
*
* We need to add ` element.focus() ` , because it ' s testable unlike ` element[0].focus ` .
*
* TODO ( ajoslin ) : This should be added in a better place later .
* /
2014-11-03 17:32:25 +01:00
2014-09-30 12:29:53 +02:00
angular . element . prototype . focus = angular . element . prototype . focus || function ( ) {
if ( this . length ) {
this [ 0 ] . focus ( ) ;
}
return this ;
} ;
2014-10-12 19:07:47 +02:00
angular . element . prototype . blur = angular . element . prototype . blur || function ( ) {
if ( this . length ) {
this [ 0 ] . blur ( ) ;
2014-09-30 12:29:53 +02:00
}
2014-10-12 19:07:47 +02:00
return this ;
2014-09-30 12:29:53 +02:00
} ;
2014-11-03 17:32:25 +01:00
} ) ( ) ;
2014-09-30 12:29:53 +02:00
2014-11-03 17:32:25 +01:00
( function ( ) {
angular . module ( 'material.decorators' , [ ] )
. config ( [ '$provide' , function ( $provide ) {
$provide . decorator ( '$$rAF' , [ '$delegate' , '$rootScope' , rAFDecorator ] ) ;
2014-09-30 12:29:53 +02:00
2014-11-03 17:32:25 +01:00
function rAFDecorator ( $$rAF , $rootScope ) {
/ * *
* Use this to debounce events that come in often .
* The debounced function will always use the * last * invocation before the
* coming frame .
*
* For example , window resize events that fire many times a second :
* If we set to use an raf - debounced callback on window resize , then
* our callback will only be fired once per frame , with the last resize
* event that happened before that frame .
*
* @ param { function } callback function to debounce
* /
$$rAF . debounce = function ( cb ) {
var queueArgs , alreadyQueued , queueCb , context ;
return function debounced ( ) {
queueArgs = arguments ;
context = this ;
queueCb = cb ;
if ( ! alreadyQueued ) {
alreadyQueued = true ;
$$rAF ( function ( ) {
queueCb . apply ( context , queueArgs ) ;
alreadyQueued = false ;
} ) ;
}
} ;
} ;
return $$rAF ;
}
} ] ) ;
} ) ( ) ;
( function ( ) {
/ *
* @ ngdoc module
* @ name material . components . animate
* @ description
*
* Ink and Popup Effects
* /
angular . module ( 'material.animations' , [ 'material.core' ] )
. service ( '$mdEffects' , [
'$rootElement' ,
'$$rAF' ,
'$sniffer' ,
'$q' ,
MdEffects
] ) ;
/ *
* @ ngdoc service
* @ name $mdEffects
* @ module material . components . animate
*
* @ description
* The ` $ mdEffects ` service provides a simple API for various
* Material Design effects .
*
* @ returns A ` $ mdEffects ` object with the following properties :
* - ` {function(element,styles,duration)} ` ` inkBar ` - starts ink bar
* animation on specified DOM element
* - ` {function(element,parentElement,clickElement)} ` ` popIn ` - animated show of element overlayed on parent element
* - ` {function(element,parentElement)} ` ` popOut ` - animated close of popup overlay
*
* /
function MdEffects ( $rootElement , $$rAF , $sniffer , $q ) {
2014-09-30 12:29:53 +02:00
var webkit = /webkit/i . test ( $sniffer . vendorPrefix ) ;
function vendorProperty ( name ) {
return webkit ?
( 'webkit' + name . charAt ( 0 ) . toUpperCase ( ) + name . substring ( 1 ) ) :
name ;
}
var self ;
// Publish API for effects...
return self = {
popIn : popIn ,
/* Constants */
TRANSITIONEND _EVENT : 'transitionend' + ( webkit ? ' webkitTransitionEnd' : '' ) ,
ANIMATIONEND _EVENT : 'animationend' + ( webkit ? ' webkitAnimationEnd' : '' ) ,
TRANSFORM : vendorProperty ( 'transform' ) ,
TRANSITION : vendorProperty ( 'transition' ) ,
TRANSITION _DURATION : vendorProperty ( 'transitionDuration' ) ,
ANIMATION _PLAY _STATE : vendorProperty ( 'animationPlayState' ) ,
ANIMATION _DURATION : vendorProperty ( 'animationDuration' ) ,
ANIMATION _NAME : vendorProperty ( 'animationName' ) ,
ANIMATION _TIMING : vendorProperty ( 'animationTimingFunction' ) ,
ANIMATION _DIRECTION : vendorProperty ( 'animationDirection' )
} ;
// **********************************************************
// API Methods
// **********************************************************
function popIn ( element , parentElement , clickElement ) {
var deferred = $q . defer ( ) ;
parentElement . append ( element ) ;
var startPos ;
if ( clickElement ) {
var clickRect = clickElement [ 0 ] . getBoundingClientRect ( ) ;
startPos = translateString (
clickRect . left - element [ 0 ] . offsetWidth ,
clickRect . top - element [ 0 ] . offsetHeight ,
0
) + ' scale(0.2)' ;
} else {
startPos = 'translate3d(0,100%,0) scale(0.5)' ;
}
element
. css ( self . TRANSFORM , startPos )
. css ( 'opacity' , 0 ) ;
$$rAF ( function ( ) {
$$rAF ( function ( ) {
element
2014-11-06 16:55:16 +01:00
. addClass ( 'md-active' )
2014-09-30 12:29:53 +02:00
. css ( self . TRANSFORM , '' )
. css ( 'opacity' , '' )
. on ( self . TRANSITIONEND _EVENT , finished ) ;
} ) ;
} ) ;
function finished ( ev ) {
//Make sure this transitionend didn't bubble up from a child
if ( ev . target === element [ 0 ] ) {
element . off ( self . TRANSITIONEND _EVENT , finished ) ;
deferred . resolve ( ) ;
}
}
return deferred . promise ;
}
// **********************************************************
// Utility Methods
// **********************************************************
function translateString ( x , y , z ) {
return 'translate3d(' + Math . floor ( x ) + 'px,' + Math . floor ( y ) + 'px,' + Math . floor ( z ) + 'px)' ;
}
}
} ) ( ) ;
2014-11-03 17:32:25 +01:00
( function ( ) {
2014-09-30 12:29:53 +02:00
angular . module ( 'material.animations' )
. directive ( 'inkRipple' , [
2014-11-03 17:32:25 +01:00
'$mdInkRipple' ,
2014-09-30 12:29:53 +02:00
InkRippleDirective
] )
2014-11-03 17:32:25 +01:00
. factory ( '$mdInkRipple' , [
2014-09-30 12:29:53 +02:00
'$window' ,
'$$rAF' ,
2014-11-03 17:32:25 +01:00
'$mdEffects' ,
2014-09-30 12:29:53 +02:00
'$timeout' ,
2014-11-03 17:32:25 +01:00
'$mdUtil' ,
2014-09-30 12:29:53 +02:00
InkRippleService
] ) ;
2014-11-03 17:32:25 +01:00
function InkRippleDirective ( $mdInkRipple ) {
2014-09-30 12:29:53 +02:00
return function ( scope , element , attr ) {
if ( attr . inkRipple == 'checkbox' ) {
2014-11-03 17:32:25 +01:00
$mdInkRipple . attachCheckboxBehavior ( element ) ;
2014-09-30 12:29:53 +02:00
} else {
2014-11-03 17:32:25 +01:00
$mdInkRipple . attachButtonBehavior ( element ) ;
2014-09-30 12:29:53 +02:00
}
} ;
}
2014-11-03 17:32:25 +01:00
function InkRippleService ( $window , $$rAF , $mdEffects , $timeout , $mdUtil ) {
2014-09-30 12:29:53 +02:00
return {
attachButtonBehavior : attachButtonBehavior ,
attachCheckboxBehavior : attachCheckboxBehavior ,
attach : attach
} ;
function attachButtonBehavior ( element ) {
return attach ( element , {
mousedown : true ,
center : false ,
animationDuration : 350 ,
mousedownPauseTime : 175 ,
animationName : 'inkRippleButton' ,
animationTimingFunction : 'linear'
} ) ;
}
function attachCheckboxBehavior ( element ) {
return attach ( element , {
mousedown : true ,
center : true ,
animationDuration : 300 ,
mousedownPauseTime : 180 ,
animationName : 'inkRippleCheckbox' ,
animationTimingFunction : 'linear'
} ) ;
}
function attach ( element , options ) {
2014-10-12 19:07:47 +02:00
// Parent element with noink attr? Abort.
if ( element . controller ( 'noink' ) ) return angular . noop ;
2014-11-03 17:32:25 +01:00
var contentParent = element . controller ( 'mdContent' ) ;
2014-10-12 19:07:47 +02:00
2014-09-30 12:29:53 +02:00
options = angular . extend ( {
mousedown : true ,
hover : true ,
focus : true ,
center : false ,
animationDuration : 300 ,
mousedownPauseTime : 150 ,
animationName : '' ,
animationTimingFunction : 'linear'
} , options || { } ) ;
var rippleContainer ;
var node = element [ 0 ] ;
2014-11-03 17:32:25 +01:00
var hammertime = new Hammer ( node ) ;
2014-09-30 12:29:53 +02:00
if ( options . mousedown ) {
2014-11-03 17:32:25 +01:00
hammertime . on ( 'hammer.input' , onInput ) ;
2014-09-30 12:29:53 +02:00
}
2014-10-12 19:07:47 +02:00
// Publish self-detach method if desired...
return function detach ( ) {
2014-11-03 17:32:25 +01:00
hammertime . destroy ( ) ;
2014-10-12 19:07:47 +02:00
if ( rippleContainer ) {
rippleContainer . remove ( ) ;
2014-09-30 12:29:53 +02:00
}
2014-10-12 19:07:47 +02:00
} ;
function rippleIsAllowed ( ) {
2014-11-03 17:32:25 +01:00
return ! element [ 0 ] . hasAttribute ( 'disabled' ) ;
2014-09-30 12:29:53 +02:00
}
2014-10-12 19:07:47 +02:00
2014-09-30 12:29:53 +02:00
function createRipple ( left , top , positionsAreAbsolute ) {
2014-11-03 17:32:25 +01:00
var rippleEl = angular . element ( '<div class="md-ripple">' )
. css ( $mdEffects . ANIMATION _DURATION , options . animationDuration + 'ms' )
. css ( $mdEffects . ANIMATION _NAME , options . animationName )
. css ( $mdEffects . ANIMATION _TIMING , options . animationTimingFunction )
. on ( $mdEffects . ANIMATIONEND _EVENT , function ( ) {
2014-10-12 19:07:47 +02:00
rippleEl . remove ( ) ;
} ) ;
2014-09-30 12:29:53 +02:00
if ( ! rippleContainer ) {
2014-11-03 17:32:25 +01:00
rippleContainer = angular . element ( '<div class="md-ripple-container">' ) ;
2014-09-30 12:29:53 +02:00
element . append ( rippleContainer ) ;
}
rippleContainer . append ( rippleEl ) ;
var containerWidth = rippleContainer . prop ( 'offsetWidth' ) ;
if ( options . center ) {
left = containerWidth / 2 ;
top = rippleContainer . prop ( 'offsetHeight' ) / 2 ;
} else if ( positionsAreAbsolute ) {
var elementRect = node . getBoundingClientRect ( ) ;
left -= elementRect . left ;
top -= elementRect . top ;
}
2014-11-03 17:32:25 +01:00
if ( contentParent ) {
top += contentParent . $element . prop ( 'scrollTop' ) ;
}
2014-09-30 12:29:53 +02:00
var css = {
'background-color' : $window . getComputedStyle ( rippleEl [ 0 ] ) . color ||
$window . getComputedStyle ( node ) . color ,
'border-radius' : ( containerWidth / 2 ) + 'px' ,
left : ( left - containerWidth / 2 ) + 'px' ,
width : containerWidth + 'px' ,
top : ( top - containerWidth / 2 ) + 'px' ,
height : containerWidth + 'px'
} ;
2014-11-03 17:32:25 +01:00
css [ $mdEffects . ANIMATION _DURATION ] = options . fadeoutDuration + 'ms' ;
2014-09-30 12:29:53 +02:00
rippleEl . css ( css ) ;
return rippleEl ;
}
2014-10-12 19:07:47 +02:00
2014-11-03 17:32:25 +01:00
var pauseTimeout ;
var rippleEl ;
function onInput ( ev ) {
if ( ev . eventType === Hammer . INPUT _START && ev . isFirst && rippleIsAllowed ( ) ) {
2014-10-12 19:07:47 +02:00
2014-11-03 17:32:25 +01:00
rippleEl = createRipple ( ev . center . x , ev . center . y , true ) ;
pauseTimeout = $timeout ( function ( ) {
rippleEl && rippleEl . css ( $mdEffects . ANIMATION _PLAY _STATE , 'paused' ) ;
} , options . mousedownPauseTime , false ) ;
2014-10-12 19:07:47 +02:00
2014-11-03 17:32:25 +01:00
rippleEl . on ( '$destroy' , function ( ) {
rippleEl = null ;
} ) ;
2014-10-12 19:07:47 +02:00
2014-11-03 17:32:25 +01:00
} else if ( ev . eventType === Hammer . INPUT _END && ev . isFinal ) {
$timeout . cancel ( pauseTimeout ) ;
rippleEl && rippleEl . css ( $mdEffects . ANIMATION _PLAY _STATE , '' ) ;
2014-10-12 19:07:47 +02:00
}
}
2014-11-03 17:32:25 +01:00
2014-10-12 19:07:47 +02:00
}
}
2014-11-03 17:32:25 +01:00
} ) ( ) ;
( function ( ) {
angular . module ( 'material.animations' )
/ * *
* noink / nobar / nostretch directive : make any element that has one of
* these attributes be given a controller , so that other directives can
* ` require: ` these and see if there is a ` no<xxx> ` parent attribute .
*
* @ usage
* < hljs lang = "html" >
* < parent noink >
* < child detect - no >
* < / c h i l d >
* < / p a r e n t >
* < / h l j s >
*
* < hljs lang = "js" >
* myApp . directive ( 'detectNo' , function ( ) {
* return {
* require : [ '^?noink' , ^ ? nobar ' ] ,
* link : function ( scope , element , attr , ctrls ) {
* var noinkCtrl = ctrls [ 0 ] ;
* var nobarCtrl = ctrls [ 1 ] ;
* if ( noInkCtrl ) {
* alert ( "the noink flag has been specified on an ancestor!" ) ;
* }
* if ( nobarCtrl ) {
* alert ( "the nobar flag has been specified on an ancestor!" ) ;
* }
* }
* } ;
* } ) ;
* < / h l j s >
* /
. directive ( {
noink : attrNoDirective ( ) ,
nobar : attrNoDirective ( ) ,
nostretch : attrNoDirective ( )
} ) ;
function attrNoDirective ( ) {
return function ( ) {
return {
controller : angular . noop
} ;
} ;
}
} ) ( ) ;
2014-10-12 19:07:47 +02:00
2014-11-06 16:55:16 +01:00
( function ( ) {
/ *
* @ ngdoc module
* @ name material . components . backdrop
* @ description Backdrop
* /
/ * *
* @ ngdoc directive
* @ name mdBackdrop
* @ module material . components . backdrop
*
* @ restrict E
*
* @ description
* ` <md-backdrop> ` is a backdrop element used by other coponents , such as dialog and bottom sheet .
* Apply class ` opaque ` to make the backdrop use the theme backdrop color .
*
* /
angular . module ( 'material.components.backdrop' , [
'material.services.theming'
] )
. directive ( 'mdBackdrop' , [
'$mdTheming' ,
BackdropDirective
] ) ;
function BackdropDirective ( $mdTheming ) {
return $mdTheming ;
}
} ) ( ) ;
2014-11-03 17:32:25 +01:00
( function ( ) {
2014-10-12 19:07:47 +02:00
/ * *
* @ ngdoc module
2014-11-03 17:32:25 +01:00
* @ name material . components . bottomSheet
2014-10-12 19:07:47 +02:00
* @ description
2014-11-03 17:32:25 +01:00
* BottomSheet
2014-10-12 19:07:47 +02:00
* /
2014-11-03 17:32:25 +01:00
angular . module ( 'material.components.bottomSheet' , [
2014-11-06 16:55:16 +01:00
'material.components.backdrop' ,
'material.services.interimElement' ,
'material.services.theming'
2014-10-12 19:07:47 +02:00
] )
2014-11-03 17:32:25 +01:00
. directive ( 'mdBottomSheet' , [
MdBottomSheetDirective
] )
. factory ( '$mdBottomSheet' , [
'$$interimElement' ,
'$animate' ,
'$mdEffects' ,
'$timeout' ,
2014-10-12 19:07:47 +02:00
'$$rAF' ,
2014-11-06 16:55:16 +01:00
'$compile' ,
'$mdTheming' ,
2014-11-03 17:32:25 +01:00
MdBottomSheet
2014-10-12 19:07:47 +02:00
] ) ;
2014-11-03 17:32:25 +01:00
function MdBottomSheetDirective ( ) {
return {
restrict : 'E'
} ;
}
2014-10-12 19:07:47 +02:00
/ * *
2014-11-03 17:32:25 +01:00
* @ ngdoc service
* @ name $mdBottomSheet
* @ module material . components . bottomSheet
2014-10-12 19:07:47 +02:00
*
* @ description
2014-11-03 17:32:25 +01:00
* ` $ mdBottomSheet ` opens a bottom sheet over the app and provides a simple promise API .
2014-10-12 19:07:47 +02:00
*
2014-11-03 17:32:25 +01:00
* # # # Restrictions
*
* - The bottom sheet ' s template must have an outer ` <md-bottom-sheet> ` element .
2014-11-06 16:55:16 +01:00
* - Add the ` md-grid ` class to the bottom sheet for a grid layout .
* - Add the ` md-list ` class to the bottom sheet for a list layout .
2014-11-03 17:32:25 +01:00
*
* @ usage
* < hljs lang = "html" >
* < div ng - controller = "MyController" >
* < md - button ng - click = "openBottomSheet()" >
* Open a Bottom Sheet !
* < / m d - b u t t o n >
* < / d i v >
* < / h l j s >
* < hljs lang = "js" >
* var app = angular . module ( 'app' , [ 'ngMaterial' ] ) ;
* app . controller ( 'MyController' , function ( $scope , $mdBottomSheet ) {
* $scope . openBottomSheet = function ( ) {
* $mdBottomSheet . show ( {
* template : '<md-bottom-sheet>Hello!</md-bottom-sheet>'
* } ) ;
* } ;
* } ) ;
* < / h l j s >
2014-10-12 19:07:47 +02:00
* /
2014-11-03 17:32:25 +01:00
/ * *
* @ ngdoc method
* @ name $mdBottomSheet # show
*
* @ description
* Show a bottom sheet with the specified options .
*
* @ param { object } options An options object , with the following properties :
*
* - ` templateUrl ` - ` {string=} ` : The url of an html template file that will
* be used as the content of the bottom sheet . Restrictions : the template must
* have an outer ` md-bottom-sheet ` element .
* - ` template ` - ` {string=} ` : Same as templateUrl , except this is an actual
* template string .
* - ` controller ` - ` {string=} ` : The controller to associate with this bottom sheet .
* - ` locals ` - ` {string=} ` : An object containing key / value pairs . The keys will
* be used as names of values to inject into the controller . For example ,
* ` locals: {three: 3} ` would inject ` three ` into the controller with the value
* of 3.
* - ` targetEvent ` - ` {DOMClickEvent=} ` : A click ' s event object . When passed in as an option ,
* the location of the click will be used as the starting point for the opening animation
* of the the dialog .
* - ` resolve ` - ` {object=} ` : Similar to locals , except it takes promises as values
* and the bottom sheet will not open until the promises resolve .
* - ` controllerAs ` - ` {string=} ` : An alias to assign the controller to on the scope .
*
* @ returns { promise } A promise that can be resolved with ` $ mdBottomSheet.hide() ` or
* rejected with ` $ mdBottomSheet.cancel() ` .
* /
2014-10-12 19:07:47 +02:00
2014-11-03 17:32:25 +01:00
/ * *
* @ ngdoc method
* @ name $mdBottomSheet # hide
*
* @ description
* Hide the existing bottom sheet and resolve the promise returned from
* ` $ mdBottomSheet.show() ` .
*
* @ param { *= } response An argument for the resolved promise .
*
* /
2014-10-12 19:07:47 +02:00
2014-11-03 17:32:25 +01:00
/ * *
* @ ngdoc method
* @ name $mdBottomSheet # cancel
*
* @ description
* Hide the existing bottom sheet and reject the promise returned from
* ` $ mdBottomSheet.show() ` .
*
* @ param { *= } response An argument for the rejected promise .
*
* /
2014-10-12 19:07:47 +02:00
2014-11-06 16:55:16 +01:00
function MdBottomSheet ( $$interimElement , $animate , $mdEffects , $timeout , $$rAF , $compile , $mdTheming ) {
2014-11-03 17:32:25 +01:00
var backdrop ;
2014-10-12 19:07:47 +02:00
2014-11-03 17:32:25 +01:00
var $mdBottomSheet ;
return $mdBottomSheet = $$interimElement ( {
2014-11-06 16:55:16 +01:00
themable : true ,
2014-11-03 17:32:25 +01:00
targetEvent : null ,
onShow : onShow ,
onRemove : onRemove ,
} ) ;
2014-10-12 19:07:47 +02:00
2014-11-03 17:32:25 +01:00
function onShow ( scope , element , options ) {
// Add a backdrop that will close on click
2014-11-06 16:55:16 +01:00
backdrop = $compile ( '<md-backdrop class="md-opaque ng-enter">' ) ( scope ) ;
2014-11-03 17:32:25 +01:00
backdrop . on ( 'click touchstart' , function ( ) {
$timeout ( $mdBottomSheet . cancel ) ;
} ) ;
2014-11-06 16:55:16 +01:00
$mdTheming . inherit ( backdrop , options . parent ) ;
2014-10-12 19:07:47 +02:00
2014-11-03 17:32:25 +01:00
$animate . enter ( backdrop , options . parent , null ) ;
2014-10-12 19:07:47 +02:00
2014-11-03 17:32:25 +01:00
var bottomSheet = new BottomSheet ( element ) ;
options . bottomSheet = bottomSheet ;
2014-10-12 19:07:47 +02:00
2014-11-03 17:32:25 +01:00
// Give up focus on calling item
options . targetEvent && angular . element ( options . targetEvent . target ) . blur ( ) ;
2014-11-06 16:55:16 +01:00
$mdTheming . inherit ( bottomSheet . element , options . parent ) ;
2014-10-12 19:07:47 +02:00
2014-11-03 17:32:25 +01:00
return $animate . enter ( bottomSheet . element , options . parent ) ;
2014-10-12 19:07:47 +02:00
2014-11-03 17:32:25 +01:00
}
2014-10-12 19:07:47 +02:00
2014-11-03 17:32:25 +01:00
function onRemove ( scope , element , options ) {
var bottomSheet = options . bottomSheet ;
$animate . leave ( backdrop ) ;
return $animate . leave ( bottomSheet . element ) . then ( function ( ) {
bottomSheet . cleanup ( ) ;
2014-10-12 19:07:47 +02:00
2014-11-03 17:32:25 +01:00
// Restore focus
options . targetEvent && angular . element ( options . targetEvent . target ) . focus ( ) ;
} ) ;
}
2014-10-12 19:07:47 +02:00
2014-11-03 17:32:25 +01:00
/ * *
* BottomSheet class to apply bottom - sheet behavior to an element
* /
function BottomSheet ( element ) {
var MAX _OFFSET = 80 ; // amount past the bottom of the element that we can drag down, this is same as in _bottomSheet.scss
var WIGGLE _AMOUNT = 20 ; // point where it starts to get "harder" to drag
var CLOSING _VELOCITY = 10 ; // how fast we need to flick down to close the sheet
var startY , lastY , velocity , transitionDelay , startTarget ;
2014-10-12 19:07:47 +02:00
2014-11-03 17:32:25 +01:00
// coercion incase $mdCompiler returns multiple elements
element = element . eq ( 0 ) ;
2014-10-12 19:07:47 +02:00
2014-11-03 17:32:25 +01:00
element . on ( 'touchstart' , onTouchStart ) ;
element . on ( 'touchmove' , onTouchMove ) ;
element . on ( 'touchend' , onTouchEnd ) ;
2014-10-12 19:07:47 +02:00
2014-11-03 17:32:25 +01:00
return {
element : element ,
cleanup : function cleanup ( ) {
element . off ( 'touchstart' , onTouchStart ) ;
element . off ( 'touchmove' , onTouchMove ) ;
element . off ( 'touchend' , onTouchEnd ) ;
2014-10-12 19:07:47 +02:00
}
2014-11-03 17:32:25 +01:00
} ;
function onTouchStart ( e ) {
e . preventDefault ( ) ;
startTarget = e . target ;
startY = getY ( e ) ;
// Disable transitions on transform so that it feels fast
transitionDelay = element . css ( $mdEffects . TRANSITION _DURATION ) ;
element . css ( $mdEffects . TRANSITION _DURATION , '0s' ) ;
2014-10-12 19:07:47 +02:00
}
2014-11-03 17:32:25 +01:00
function onTouchEnd ( e ) {
// Re-enable the transitions on transforms
element . css ( $mdEffects . TRANSITION _DURATION , transitionDelay ) ;
2014-10-12 19:07:47 +02:00
2014-11-03 17:32:25 +01:00
var currentY = getY ( e ) ;
// If we didn't scroll much, and we didn't change targets, assume its a click
if ( Math . abs ( currentY - startY ) < 5 && e . target == startTarget ) {
angular . element ( e . target ) . triggerHandler ( 'click' ) ;
} else {
// If they went fast enough, trigger a close.
if ( velocity > CLOSING _VELOCITY ) {
$timeout ( $mdBottomSheet . cancel ) ;
2014-10-12 19:07:47 +02:00
2014-11-03 17:32:25 +01:00
// Otherwise, untransform so that we go back to our normal position
} else {
setTransformY ( undefined ) ;
}
2014-10-12 19:07:47 +02:00
}
}
2014-11-03 17:32:25 +01:00
function onTouchMove ( e ) {
var currentY = getY ( e ) ;
var delta = currentY - startY ;
2014-10-12 19:07:47 +02:00
2014-11-03 17:32:25 +01:00
velocity = currentY - lastY ;
lastY = currentY ;
// Do some conversion on delta to get a friction-like effect
delta = adjustedDelta ( delta ) ;
setTransformY ( delta + MAX _OFFSET ) ;
}
2014-10-12 19:07:47 +02:00
2014-11-03 17:32:25 +01:00
/ * *
* Helper function to find the Y aspect of various touch events .
* * /
function getY ( e ) {
var touch = e . touches && e . touches . length ? e . touches [ 0 ] : e . changedTouches [ 0 ] ;
return touch . clientY ;
}
2014-10-12 19:07:47 +02:00
2014-11-03 17:32:25 +01:00
/ * *
* Transform the element along the y - axis
* * /
function setTransformY ( amt ) {
if ( amt === null || amt === undefined ) {
element . css ( $mdEffects . TRANSFORM , '' ) ;
} else {
element . css ( $mdEffects . TRANSFORM , 'translate3d(0, ' + amt + 'px, 0)' ) ;
2014-10-12 19:07:47 +02:00
}
}
2014-11-03 17:32:25 +01:00
// Returns a new value for delta that will never exceed MAX_OFFSET_AMOUNT
// Will get harder to exceed it as you get closer to it
function adjustedDelta ( delta ) {
if ( delta < 0 && delta < - MAX _OFFSET + WIGGLE _AMOUNT ) {
delta = - delta ;
var base = MAX _OFFSET - WIGGLE _AMOUNT ;
delta = Math . max ( - MAX _OFFSET , - Math . min ( MAX _OFFSET - 5 , base + ( WIGGLE _AMOUNT * ( delta - base ) ) / MAX _OFFSET ) - delta / 50 ) ;
}
2014-10-12 19:07:47 +02:00
2014-11-03 17:32:25 +01:00
return delta ;
}
}
2014-10-12 19:07:47 +02:00
2014-11-03 17:32:25 +01:00
}
} ) ( ) ;
2014-10-12 19:07:47 +02:00
2014-11-03 17:32:25 +01:00
( function ( ) {
/ * *
* @ ngdoc module
* @ name material . components . button
* @ description
*
* Button
* /
angular . module ( 'material.components.button' , [
'material.core' ,
'material.animations' ,
2014-11-06 16:55:16 +01:00
'material.services.aria' ,
'material.services.theming'
2014-11-03 17:32:25 +01:00
] )
. directive ( 'mdButton' , [
'ngHrefDirective' ,
'$mdInkRipple' ,
'$mdAria' ,
'$mdUtil' ,
2014-11-06 16:55:16 +01:00
'$mdTheming' ,
2014-11-03 17:32:25 +01:00
MdButtonDirective
] ) ;
2014-10-12 19:07:47 +02:00
2014-11-03 17:32:25 +01:00
/ * *
* @ ngdoc directive
* @ name mdButton
* @ module material . components . button
*
* @ restrict E
*
* @ description
* ` <md-button> ` is a button directive with optional ink ripples ( default enabled ) .
*
* @ param { boolean = } noink If present , disable ripple ink effects .
* @ param { boolean = } disabled If present , disable tab selection .
* @ param { string = } type Optional attribute to specific button types ( useful for forms ) ; such as 'submit' , etc .
* @ param { string = } ng - href Optional attribute to support both ARIA and link navigation
* @ param { string = } href Optional attribute to support both ARIA and link navigation
* @ param { string = } ariaLabel Publish the button label used by screen - readers for accessibility . Defaults to the button ' s text .
*
* @ usage
* < hljs lang = "html" >
* < md - button > Button < / m d - b u t t o n >
* < br / >
* < md - button noink class = "md-button-colored" >
* Button ( noInk )
* < / m d - b u t t o n >
* < br / >
* < md - button disabled class = "md-button-colored" >
* Colored ( disabled )
* < / m d - b u t t o n >
* < / h l j s >
* /
2014-11-06 16:55:16 +01:00
function MdButtonDirective ( ngHrefDirectives , $mdInkRipple , $mdAria , $mdUtil , $mdTheming ) {
2014-11-03 17:32:25 +01:00
var ngHrefDirective = ngHrefDirectives [ 0 ] ;
2014-10-12 19:07:47 +02:00
2014-11-03 17:32:25 +01:00
return {
restrict : 'E' ,
compile : function ( element , attr ) {
var innerElement ;
var attributesToCopy ;
2014-10-12 19:07:47 +02:00
2014-11-06 16:55:16 +01:00
2014-11-03 17:32:25 +01:00
// Add an inner anchor if the element has a `href` or `ngHref` attribute,
// so this element can be clicked like a normal `<a>`.
if ( attr . ngHref || attr . href ) {
innerElement = angular . element ( '<a>' ) ;
attributesToCopy = [ 'ng-href' , 'href' , 'rel' , 'target' ] ;
// Otherwise, just add an inner button element (for form submission etc)
2014-10-12 19:07:47 +02:00
} else {
2014-11-03 17:32:25 +01:00
innerElement = angular . element ( '<button>' ) ;
attributesToCopy = [ 'type' , 'disabled' , 'ng-disabled' , 'form' ] ;
2014-10-12 19:07:47 +02:00
}
2014-11-03 17:32:25 +01:00
angular . forEach ( attributesToCopy , function ( name ) {
var camelCaseName = $mdUtil . camelCase ( name ) ;
if ( attr . hasOwnProperty ( camelCaseName ) ) {
innerElement . attr ( name , attr [ camelCaseName ] ) ;
}
} ) ;
innerElement
. addClass ( 'md-button-inner' )
. append ( element . contents ( ) )
// Since we're always passing focus to the inner element,
// add a focus class to the outer element so we can still style
// it with focus.
. on ( 'focus' , function ( ) {
element . addClass ( 'focus' ) ;
} )
. on ( 'blur' , function ( ) {
element . removeClass ( 'focus' ) ;
} ) ;
element .
append ( innerElement )
. attr ( 'tabIndex' , - 1 )
//Always pass focus to innerElement
. on ( 'focus' , function ( ) {
innerElement . focus ( ) ;
} ) ;
return function postLink ( scope , element , attr ) {
2014-11-06 16:55:16 +01:00
$mdTheming ( element ) ;
2014-11-03 17:32:25 +01:00
$mdAria . expect ( element , 'aria-label' , element . text ( ) ) ;
$mdInkRipple . attachButtonBehavior ( element ) ;
} ;
2014-10-12 19:07:47 +02:00
}
2014-11-03 17:32:25 +01:00
} ;
2014-10-12 19:07:47 +02:00
}
2014-11-03 17:32:25 +01:00
} ) ( ) ;
2014-10-12 19:07:47 +02:00
2014-11-03 17:32:25 +01:00
( function ( ) {
2014-10-12 19:07:47 +02:00
/ * *
* @ ngdoc module
2014-11-03 17:32:25 +01:00
* @ name material . components . card
*
2014-10-12 19:07:47 +02:00
* @ description
2014-11-03 17:32:25 +01:00
* Card components .
2014-10-12 19:07:47 +02:00
* /
2014-11-03 17:32:25 +01:00
angular . module ( 'material.components.card' , [
2014-10-12 19:07:47 +02:00
] )
2014-11-03 17:32:25 +01:00
. directive ( 'mdCard' , [
mdCardDirective
] ) ;
2014-10-12 19:07:47 +02:00
2014-11-03 17:32:25 +01:00
/ * *
* @ ngdoc directive
* @ name mdCard
* @ module material . components . card
2014-10-12 19:07:47 +02:00
*
2014-11-03 17:32:25 +01:00
* @ restrict E
2014-10-12 19:07:47 +02:00
*
2014-11-03 17:32:25 +01:00
* @ description
* The ` <md-card> ` directive is a container element used within ` <md-content> ` containers .
2014-10-12 19:07:47 +02:00
*
2014-11-03 17:32:25 +01:00
* Cards have constant width and variable heights ; where the maximum height is limited to what can
* fit within a single view on a platform , but it can temporarily expand as needed
2014-10-12 19:07:47 +02:00
*
* @ usage
* < hljs lang = "html" >
2014-11-03 17:32:25 +01:00
* < md - card >
* < img src = "/img/washedout.png" class = "md-card-image" >
* < h2 > Paracosm < / h 2 >
* < p >
* The titles of Washed Out 's breakthrough song and the first single from Paracosm share the * two most important words in Ernest Greene' s musical language : feel it . It ' s a simple request , as well ...
* < / p >
* < / m d - c a r d >
2014-10-12 19:07:47 +02:00
* < / h l j s >
2014-11-03 17:32:25 +01:00
*
2014-10-12 19:07:47 +02:00
* /
2014-11-03 17:32:25 +01:00
function mdCardDirective ( ) {
return {
restrict : 'E' ,
link : function ( $scope , $element , $attr ) {
}
} ;
}
} ) ( ) ;
2014-10-12 19:07:47 +02:00
2014-11-03 17:32:25 +01:00
( function ( ) {
/ * *
* @ ngdoc module
* @ name material . components . checkbox
* @ description Checkbox module !
2014-10-12 19:07:47 +02:00
* /
2014-11-03 17:32:25 +01:00
angular . module ( 'material.components.checkbox' , [
'material.core' ,
'material.animations' ,
2014-11-06 16:55:16 +01:00
'material.services.theming' ,
2014-11-03 17:32:25 +01:00
'material.services.aria'
] )
. directive ( 'mdCheckbox' , [
'inputDirective' ,
'$mdInkRipple' ,
'$mdAria' ,
'$mdConstant' ,
2014-11-06 16:55:16 +01:00
'$mdTheming' ,
2014-11-03 17:32:25 +01:00
MdCheckboxDirective
] ) ;
2014-10-12 19:07:47 +02:00
/ * *
2014-11-03 17:32:25 +01:00
* @ ngdoc directive
* @ name mdCheckbox
* @ module material . components . checkbox
* @ restrict E
2014-10-12 19:07:47 +02:00
*
* @ description
2014-11-03 17:32:25 +01:00
* The checkbox directive is used like the normal [ angular checkbox ] ( https : //docs.angularjs.org/api/ng/input/input%5Bcheckbox%5D).
2014-10-12 19:07:47 +02:00
*
2014-11-03 17:32:25 +01:00
* @ param { string } ngModel Assignable angular expression to data - bind to .
* @ param { string = } name Property name of the form under which the control is published .
* @ param { expression = } ngTrueValue The value to which the expression should be set when selected .
* @ param { expression = } ngFalseValue The value to which the expression should be set when not selected .
* @ param { string = } ngChange Angular expression to be executed when input changes due to user interaction with the input element .
* @ param { boolean = } noink Use of attribute indicates use of ripple ink effects
* @ param { boolean = } disabled Use of attribute indicates the switch is disabled : no ink effects and not selectable
* @ param { string = } ariaLabel Publish the button label used by screen - readers for accessibility . Defaults to the checkbox ' s text .
2014-10-12 19:07:47 +02:00
*
2014-11-03 17:32:25 +01:00
* @ usage
* < hljs lang = "html" >
* < md - checkbox ng - model = "isChecked" aria - label = "Finished?" >
* Finished ?
* < / m d - c h e c k b o x >
2014-10-12 19:07:47 +02:00
*
2014-11-03 17:32:25 +01:00
* < md - checkbox noink ng - model = "hasInk" aria - label = "No Ink Effects" >
* No Ink Effects
* < / m d - c h e c k b o x >
*
* < md - checkbox disabled ng - model = "isDisabled" aria - label = "Disabled" >
* Disabled
* < / m d - c h e c k b o x >
2014-10-12 19:07:47 +02:00
*
2014-11-03 17:32:25 +01:00
* < / h l j s >
2014-10-12 19:07:47 +02:00
*
* /
2014-11-06 16:55:16 +01:00
function MdCheckboxDirective ( inputDirectives , $mdInkRipple , $mdAria , $mdConstant , $mdTheming ) {
2014-11-03 17:32:25 +01:00
var inputDirective = inputDirectives [ 0 ] ;
2014-10-12 19:07:47 +02:00
2014-11-03 17:32:25 +01:00
var CHECKED _CSS = 'md-checked' ;
2014-10-12 19:07:47 +02:00
2014-11-03 17:32:25 +01:00
return {
restrict : 'E' ,
transclude : true ,
require : '?ngModel' ,
template :
'<div class="md-container" ink-ripple="checkbox">' +
'<div class="md-icon"></div>' +
'</div>' +
'<div ng-transclude class="md-label"></div>' ,
compile : compile
} ;
2014-10-12 19:07:47 +02:00
2014-11-03 17:32:25 +01:00
// **********************************************************
// Private Methods
// **********************************************************
2014-10-12 19:07:47 +02:00
2014-11-03 17:32:25 +01:00
function compile ( tElement , tAttrs ) {
2014-10-12 19:07:47 +02:00
2014-11-03 17:32:25 +01:00
tAttrs . type = 'checkbox' ;
tAttrs . tabIndex = 0 ;
tElement . attr ( 'role' , tAttrs . type ) ;
2014-10-12 19:07:47 +02:00
2014-11-03 17:32:25 +01:00
return function postLink ( scope , element , attr , ngModelCtrl ) {
var checked = false ;
2014-11-06 16:55:16 +01:00
$mdTheming ( element ) ;
2014-10-12 19:07:47 +02:00
2014-11-03 17:32:25 +01:00
// Create a mock ngModel if the user doesn't provide one
ngModelCtrl = ngModelCtrl || {
$setViewValue : function ( value ) {
this . $viewValue = value ;
} ,
$parsers : [ ] ,
$formatters : [ ]
} ;
2014-10-12 19:07:47 +02:00
2014-11-06 16:55:16 +01:00
$mdAria . expect ( tElement , 'aria-label' , true ) ;
2014-11-03 17:32:25 +01:00
// Reuse the original input[type=checkbox] directive from Angular core.
// This is a bit hacky as we need our own event listener and own render
// function.
inputDirective . link . pre ( scope , {
on : angular . noop ,
0 : { }
} , attr , [ ngModelCtrl ] ) ;
2014-10-12 19:07:47 +02:00
2014-11-03 17:32:25 +01:00
element . on ( 'click' , listener ) ;
element . on ( 'keypress' , keypressHandler ) ;
ngModelCtrl . $render = render ;
2014-10-12 19:07:47 +02:00
2014-11-03 17:32:25 +01:00
function keypressHandler ( ev ) {
if ( ev . which === $mdConstant . KEY _CODE . SPACE ) {
ev . preventDefault ( ) ;
listener ( ev ) ;
2014-10-12 19:07:47 +02:00
}
}
2014-11-03 17:32:25 +01:00
function listener ( ev ) {
if ( element [ 0 ] . hasAttribute ( 'disabled' ) ) return ;
2014-10-12 19:07:47 +02:00
2014-11-03 17:32:25 +01:00
scope . $apply ( function ( ) {
checked = ! checked ;
ngModelCtrl . $setViewValue ( checked , ev && ev . type ) ;
ngModelCtrl . $render ( ) ;
} ) ;
2014-10-12 19:07:47 +02:00
}
2014-11-03 17:32:25 +01:00
function render ( ) {
checked = ngModelCtrl . $viewValue ;
if ( checked ) {
element . addClass ( CHECKED _CSS ) ;
} else {
element . removeClass ( CHECKED _CSS ) ;
}
2014-10-12 19:07:47 +02:00
}
2014-11-03 17:32:25 +01:00
} ;
2014-10-12 19:07:47 +02:00
}
2014-09-30 12:29:53 +02:00
}
2014-11-03 17:32:25 +01:00
} ) ( ) ;
( function ( ) {
2014-09-30 12:29:53 +02:00
/ * *
* @ ngdoc module
2014-11-03 17:32:25 +01:00
* @ name material . components . content
2014-09-30 12:29:53 +02:00
*
2014-11-03 17:32:25 +01:00
* @ description
* Scrollable content
2014-09-30 12:29:53 +02:00
* /
2014-11-03 17:32:25 +01:00
angular . module ( 'material.components.content' , [
2014-11-06 16:55:16 +01:00
'material.services.theming' ,
2014-11-03 17:32:25 +01:00
'material.services.registry'
2014-09-30 12:29:53 +02:00
] )
2014-11-03 17:32:25 +01:00
. directive ( 'mdContent' , [
2014-11-06 16:55:16 +01:00
'$mdTheming' ,
2014-11-03 17:32:25 +01:00
mdContentDirective
2014-09-30 12:29:53 +02:00
] ) ;
/ * *
* @ ngdoc directive
2014-11-03 17:32:25 +01:00
* @ name mdContent
* @ module material . components . content
2014-09-30 12:29:53 +02:00
*
* @ restrict E
*
* @ description
2014-11-03 17:32:25 +01:00
* The ` <md-content> ` directive is a container element useful for scrollable content
2014-09-30 12:29:53 +02:00
*
2014-11-06 16:55:16 +01:00
* # # # Restrictions
*
* - Add the ` md-padding ` class to make the content padded .
*
2014-09-30 12:29:53 +02:00
* @ usage
* < hljs lang = "html" >
2014-11-06 16:55:16 +01:00
* < md - content class = "md-padding" >
2014-11-03 17:32:25 +01:00
* Lorem ipsum dolor sit amet , ne quod novum mei .
* < / m d - c o n t e n t >
2014-09-30 12:29:53 +02:00
* < / h l j s >
2014-11-03 17:32:25 +01:00
*
2014-09-30 12:29:53 +02:00
* /
2014-11-06 16:55:16 +01:00
function mdContentDirective ( $mdTheming ) {
2014-09-30 12:29:53 +02:00
return {
restrict : 'E' ,
2014-11-03 17:32:25 +01:00
controller : [ '$scope' , '$element' , ContentController ] ,
link : function ( $scope , $element , $attr ) {
2014-11-06 16:55:16 +01:00
$mdTheming ( $element ) ;
2014-11-03 17:32:25 +01:00
$scope . $broadcast ( '$mdContentLoaded' , $element ) ;
2014-09-30 12:29:53 +02:00
}
} ;
2014-11-03 17:32:25 +01:00
function ContentController ( $scope , $element ) {
this . $scope = $scope ;
this . $element = $element ;
}
2014-09-30 12:29:53 +02:00
}
2014-11-03 17:32:25 +01:00
} ) ( ) ;
2014-09-30 12:29:53 +02:00
2014-11-03 17:32:25 +01:00
( function ( ) {
2014-09-30 12:29:53 +02:00
/ * *
* @ ngdoc module
2014-11-03 17:32:25 +01:00
* @ name material . components . dialog
2014-09-30 12:29:53 +02:00
* /
2014-11-03 17:32:25 +01:00
angular . module ( 'material.components.dialog' , [
'material.core' ,
'material.animations' ,
2014-11-06 16:55:16 +01:00
'material.components.backdrop' ,
2014-11-03 17:32:25 +01:00
'material.services.compiler' ,
'material.services.aria' ,
'material.services.interimElement' ,
2014-11-06 16:55:16 +01:00
'material.services.theming' ,
2014-09-30 12:29:53 +02:00
] )
2014-11-03 17:32:25 +01:00
. directive ( 'mdDialog' , [
'$$rAF' ,
2014-11-06 16:55:16 +01:00
'$mdTheming' ,
2014-11-03 17:32:25 +01:00
MdDialogDirective
] )
. factory ( '$mdDialog' , [
'$timeout' ,
'$rootElement' ,
2014-11-06 16:55:16 +01:00
'$compile' ,
2014-11-03 17:32:25 +01:00
'$mdEffects' ,
'$animate' ,
'$mdAria' ,
'$$interimElement' ,
'$mdUtil' ,
'$mdConstant' ,
2014-11-06 16:55:16 +01:00
'$mdTheming' ,
2014-11-03 17:32:25 +01:00
MdDialogService
2014-09-30 12:29:53 +02:00
] ) ;
2014-11-06 16:55:16 +01:00
function MdDialogDirective ( $$rAF , $mdTheming ) {
2014-11-03 17:32:25 +01:00
return {
restrict : 'E' ,
link : function ( scope , element , attr ) {
2014-11-06 16:55:16 +01:00
$mdTheming ( element ) ;
2014-11-03 17:32:25 +01:00
$$rAF ( function ( ) {
2014-11-06 16:55:16 +01:00
var content = element [ 0 ] . querySelector ( 'md-content' ) ;
2014-11-03 17:32:25 +01:00
if ( content && content . scrollHeight > content . clientHeight ) {
2014-11-06 16:55:16 +01:00
element . addClass ( 'md-content-overflow' ) ;
2014-11-03 17:32:25 +01:00
}
} ) ;
}
} ;
}
2014-09-30 12:29:53 +02:00
/ * *
2014-11-03 17:32:25 +01:00
* @ ngdoc service
* @ name $mdDialog
* @ module material . components . dialog
2014-09-30 12:29:53 +02:00
*
* @ description
2014-11-03 17:32:25 +01:00
* ` $ mdDialog ` opens a dialog over the app and provides a simple promise API .
2014-09-30 12:29:53 +02:00
*
2014-11-03 17:32:25 +01:00
* # # # Restrictions
2014-09-30 12:29:53 +02:00
*
2014-11-03 17:32:25 +01:00
* - The dialog is always given an isolate scope .
* - The dialog ' s template must have an outer ` <md-dialog> ` element .
2014-11-06 16:55:16 +01:00
* Inside , use an ` <md-content> ` element for the dialog ' s content , and use
* an element with class ` md-actions ` for the dialog ' s actions .
2014-09-30 12:29:53 +02:00
*
* @ usage
* < hljs lang = "html" >
* < div ng - controller = "MyController" >
2014-11-03 17:32:25 +01:00
* < md - button ng - click = "openDialog($event)" >
2014-09-30 12:29:53 +02:00
* Open a Dialog from this button !
2014-11-03 17:32:25 +01:00
* < / m d - b u t t o n >
2014-09-30 12:29:53 +02:00
* < / d i v >
* < / h l j s >
2014-11-03 17:32:25 +01:00
*
2014-09-30 12:29:53 +02:00
* < hljs lang = "js" >
* var app = angular . module ( 'app' , [ 'ngMaterial' ] ) ;
2014-11-03 17:32:25 +01:00
* app . controller ( 'MyController' , function ( $scope , $mdDialog ) {
2014-09-30 12:29:53 +02:00
* $scope . openDialog = function ( $event ) {
2014-11-03 17:32:25 +01:00
* $mdDialog . show ( {
* targetEvent : $event ,
* controller : 'DialogController' ,
* template :
2014-11-06 16:55:16 +01:00
* '<md-dialog>' +
* ' <md-content>Hello!</md-content>' +
* ' <div class="md-actions">' +
2014-11-03 17:32:25 +01:00
* ' <md-button ng-click="closeDialog()">' +
* ' Close' +
* ' </md-button>' +
* ' </div>' +
* '</md-dialog>'
2014-09-30 12:29:53 +02:00
* } ) ;
* } ;
* } ) ;
2014-11-03 17:32:25 +01:00
* app . controller ( 'DialogController' , function ( $scope , $mdDialog ) {
* $scope . closeDialog = function ( ) {
* $mdDialog . hide ( ) ;
* } ;
* } ) ;
2014-09-30 12:29:53 +02:00
* < / h l j s >
*
2014-10-12 19:07:47 +02:00
* /
/ * *
*
* @ ngdoc method
2014-11-03 17:32:25 +01:00
* @ name $mdDialog # show
2014-10-12 19:07:47 +02:00
*
* @ description
2014-11-03 17:32:25 +01:00
* Show a dialog with the specified options .
*
* @ param { object } options An options object , with the following properties :
* - ` templateUrl ` - ` {string=} ` : The url of a template that will be used as the content
* of the dialog .
* - ` template ` - ` {string=} ` : Same as templateUrl , except this is an actual template string .
* - ` targetEvent ` - ` {DOMClickEvent=} ` : A click ' s event object . When passed in as an option ,
* the location of the click will be used as the starting point for the opening animation
* of the the dialog .
* - ` hasBackdrop ` - ` {boolean=} ` : Whether there should be an opaque backdrop behind the dialog .
* Default true .
* - ` clickOutsideToClose ` - ` {boolean=} ` : Whether the user can click outside the dialog to
* close it . Default true .
* - ` escapeToClose ` - ` {boolean=} ` : Whether the user can press escape to close the dialog .
* Default true .
* - ` controller ` - ` {string=} ` : The controller to associate with the dialog . The controller
* will be injected with the local ` $ hideDialog ` , which is a function used to hide the dialog .
* - ` locals ` - ` {object=} ` : An object containing key / value pairs . The keys will be used as names
* of values to inject into the controller . For example , ` locals: {three: 3} ` would inject
* ` three ` into the controller , with the value 3.
* - ` resolve ` - ` {object=} ` : Similar to locals , except it takes promises as values , and the
* toast will not open until all of the promises resolve .
* - ` controllerAs ` - ` {string=} ` : An alias to assign the controller to on the scope .
* - ` parent ` - ` {element=} ` : The element to append the dialog to . Defaults to appending
* to the root element of the application .
*
* @ returns { promise } A promise that can be resolved with ` $ mdDialog.hide() ` or
* rejected with ` mdDialog.cancel() ` .
2014-09-30 12:29:53 +02:00
* /
2014-10-12 19:07:47 +02:00
/ * *
* @ ngdoc method
2014-11-03 17:32:25 +01:00
* @ name $mdDialog # hide
2014-10-12 19:07:47 +02:00
*
* @ description
2014-11-03 17:32:25 +01:00
* Hide an existing dialog and resolve the promise returned from ` $ mdDialog.show() ` .
2014-10-12 19:07:47 +02:00
*
2014-11-03 17:32:25 +01:00
* @ param { *= } response An argument for the resolved promise .
2014-10-12 19:07:47 +02:00
*
* /
2014-09-30 12:29:53 +02:00
2014-10-12 19:07:47 +02:00
/ * *
* @ ngdoc method
2014-11-03 17:32:25 +01:00
* @ name $mdDialog # cancel
2014-10-12 19:07:47 +02:00
*
* @ description
2014-11-03 17:32:25 +01:00
* Hide an existing dialog and reject the promise returned from ` $ mdDialog.show() ` .
2014-10-12 19:07:47 +02:00
*
2014-11-03 17:32:25 +01:00
* @ param { *= } response An argument for the rejected promise .
2014-10-12 19:07:47 +02:00
*
* /
2014-09-30 12:29:53 +02:00
2014-11-06 16:55:16 +01:00
function MdDialogService ( $timeout , $rootElement , $compile , $mdEffects , $animate , $mdAria , $$interimElement , $mdUtil , $mdConstant , $mdTheming ) {
2014-10-12 19:07:47 +02:00
var $dialogService ;
return $dialogService = $$interimElement ( {
hasBackdrop : true ,
isolateScope : true ,
onShow : onShow ,
onRemove : onRemove ,
clickOutsideToClose : true ,
escapeToClose : true ,
targetEvent : null ,
transformTemplate : function ( template ) {
2014-11-03 17:32:25 +01:00
return '<div class="md-dialog-container">' + template + '</div>' ;
2014-10-12 19:07:47 +02:00
}
} ) ;
function onShow ( scope , element , options ) {
2014-09-30 12:29:53 +02:00
// Incase the user provides a raw dom element, always wrap it in jqLite
2014-10-12 19:07:47 +02:00
options . parent = angular . element ( options . parent ) ;
2014-09-30 12:29:53 +02:00
2014-10-12 19:07:47 +02:00
options . popInTarget = angular . element ( ( options . targetEvent || { } ) . target ) ;
var closeButton = findCloseButton ( ) ;
2014-09-30 12:29:53 +02:00
2014-11-03 17:32:25 +01:00
configureAria ( element . find ( 'md-dialog' ) ) ;
2014-09-30 12:29:53 +02:00
2014-10-12 19:07:47 +02:00
if ( options . hasBackdrop ) {
2014-11-06 16:55:16 +01:00
options . backdrop = $compile ( '<md-backdrop class="md-opaque ng-enter">' ) ( scope ) ;
$mdTheming . inherit ( options . backdrop , options . parent ) ;
$animate . enter ( options . backdrop , options . parent , null ) ;
2014-10-12 19:07:47 +02:00
}
2014-09-30 12:29:53 +02:00
2014-11-03 17:32:25 +01:00
return $mdEffects . popIn (
2014-10-12 19:07:47 +02:00
element ,
options . parent ,
options . popInTarget . length && options . popInTarget
)
. then ( function ( ) {
if ( options . escapeToClose ) {
options . rootElementKeyupCallback = function ( e ) {
2014-11-03 17:32:25 +01:00
if ( e . keyCode === $mdConstant . KEY _CODE . ESCAPE ) {
2014-10-12 19:07:47 +02:00
$timeout ( $dialogService . cancel ) ;
}
} ;
2014-09-30 12:29:53 +02:00
2014-10-12 19:07:47 +02:00
$rootElement . on ( 'keyup' , options . rootElementKeyupCallback ) ;
2014-09-30 12:29:53 +02:00
}
2014-10-12 19:07:47 +02:00
if ( options . clickOutsideToClose ) {
options . dialogClickOutsideCallback = function ( e ) {
// Only close if we click the flex container outside the backdrop
if ( e . target === element [ 0 ] ) {
$timeout ( $dialogService . cancel ) ;
2014-09-30 12:29:53 +02:00
}
2014-10-12 19:07:47 +02:00
} ;
2014-09-30 12:29:53 +02:00
2014-10-12 19:07:47 +02:00
element . on ( 'click' , options . dialogClickOutsideCallback ) ;
}
closeButton . focus ( ) ;
} ) ;
2014-09-30 12:29:53 +02:00
2014-10-12 19:07:47 +02:00
function findCloseButton ( ) {
//If no element with class dialog-close, try to find the last
2014-11-06 16:55:16 +01:00
//button child in md-actions and assume it is a close button
2014-10-12 19:07:47 +02:00
var closeButton = element [ 0 ] . querySelector ( '.dialog-close' ) ;
if ( ! closeButton ) {
2014-11-06 16:55:16 +01:00
var actionButtons = element [ 0 ] . querySelectorAll ( '.md-actions button' ) ;
2014-10-12 19:07:47 +02:00
closeButton = actionButtons [ actionButtons . length - 1 ] ;
2014-09-30 12:29:53 +02:00
}
2014-10-12 19:07:47 +02:00
return angular . element ( closeButton ) ;
}
2014-09-30 12:29:53 +02:00
2014-10-12 19:07:47 +02:00
}
function onRemove ( scope , element , options ) {
if ( options . backdrop ) {
$animate . leave ( options . backdrop ) ;
element . data ( 'backdrop' , undefined ) ;
}
if ( options . escapeToClose ) {
$rootElement . off ( 'keyup' , options . rootElementKeyupCallback ) ;
}
if ( options . clickOutsideToClose ) {
element . off ( 'click' , options . dialogClickOutsideCallback ) ;
}
return $animate . leave ( element ) . then ( function ( ) {
element . remove ( ) ;
options . popInTarget && options . popInTarget . focus ( ) ;
2014-09-30 12:29:53 +02:00
} ) ;
2014-10-12 19:07:47 +02:00
}
2014-09-30 12:29:53 +02:00
2014-10-12 19:07:47 +02:00
/ * *
* Inject ARIA - specific attributes appropriate for Dialogs
* /
function configureAria ( element ) {
element . attr ( {
'role' : 'dialog'
} ) ;
2014-09-30 12:29:53 +02:00
2014-11-06 16:55:16 +01:00
var dialogContent = element . find ( 'md-content' ) ;
2014-10-12 19:07:47 +02:00
if ( dialogContent . length === 0 ) {
dialogContent = element ;
2014-09-30 12:29:53 +02:00
}
2014-11-03 17:32:25 +01:00
var defaultText = $mdUtil . stringFromTextBody ( dialogContent . text ( ) , 3 ) ;
2014-11-06 16:55:16 +01:00
$mdAria . expect ( element , 'aria-label' , true , defaultText ) ;
2014-09-30 12:29:53 +02:00
}
}
2014-11-03 17:32:25 +01:00
} ) ( ) ;
2014-09-30 12:29:53 +02:00
2014-11-03 17:32:25 +01:00
( function ( ) {
2014-09-30 12:29:53 +02:00
/ * *
* @ ngdoc module
2014-11-03 17:32:25 +01:00
* @ name material . components . divider
* @ description Divider module !
2014-09-30 12:29:53 +02:00
* /
2014-11-03 17:32:25 +01:00
angular . module ( 'material.components.divider' , [
'material.animations' ,
2014-11-06 16:55:16 +01:00
'material.services.aria' ,
'material.services.theming'
2014-11-03 17:32:25 +01:00
] )
2014-11-06 16:55:16 +01:00
. directive ( 'mdDivider' , [
'$mdTheming' ,
MdDividerDirective
] ) ;
2014-11-03 17:32:25 +01:00
function MdDividerController ( ) { }
2014-09-30 12:29:53 +02:00
/ * *
* @ ngdoc directive
2014-11-03 17:32:25 +01:00
* @ name mdDivider
* @ module material . components . divider
2014-09-30 12:29:53 +02:00
* @ restrict E
2014-11-03 17:32:25 +01:00
*
2014-09-30 12:29:53 +02:00
* @ description
2014-11-03 17:32:25 +01:00
* Dividers group and separate content within lists and page layouts using strong visual and spatial distinctions . This divider is a thin rule , lightweight enough to not distract the user from content .
2014-09-30 12:29:53 +02:00
*
2014-11-03 17:32:25 +01:00
* @ param { boolean = } inset Add this attribute to activate the inset divider style .
* @ usage
2014-09-30 12:29:53 +02:00
* < hljs lang = "html" >
2014-11-03 17:32:25 +01:00
* < md - divider > < / m d - d i v i d e r >
*
* < md - divider inset > < / m d - d i v i d e r >
2014-09-30 12:29:53 +02:00
* < / h l j s >
2014-11-03 17:32:25 +01:00
*
2014-09-30 12:29:53 +02:00
* /
2014-11-06 16:55:16 +01:00
function MdDividerDirective ( $mdTheming ) {
2014-09-30 12:29:53 +02:00
return {
2014-11-03 17:32:25 +01:00
restrict : 'E' ,
2014-11-06 16:55:16 +01:00
link : $mdTheming ,
2014-11-03 17:32:25 +01:00
controller : [ MdDividerController ]
2014-09-30 12:29:53 +02:00
} ;
}
2014-11-03 17:32:25 +01:00
} ) ( ) ;
2014-09-30 12:29:53 +02:00
2014-11-03 17:32:25 +01:00
( function ( ) {
/ *
2014-09-30 12:29:53 +02:00
* @ ngdoc module
* @ name material . components . icon
* @ description
* Icon
* /
angular . module ( 'material.components.icon' , [ ] )
2014-11-03 17:32:25 +01:00
. directive ( 'mdIcon' , [
mdIconDirective
2014-09-30 12:29:53 +02:00
] ) ;
2014-11-03 17:32:25 +01:00
/ *
2014-09-30 12:29:53 +02:00
* @ ngdoc directive
2014-11-03 17:32:25 +01:00
* @ name mdIcon
2014-09-30 12:29:53 +02:00
* @ module material . components . icon
*
* @ restrict E
*
* @ description
2014-11-03 17:32:25 +01:00
* The ` <md-icon> ` directive is an element useful for SVG icons
2014-09-30 12:29:53 +02:00
*
* @ usage
* < hljs lang = "html" >
2014-11-03 17:32:25 +01:00
* < md - icon icon = "/img/icons/ic_access_time_24px.svg" >
* < / m d - i c o n >
2014-09-30 12:29:53 +02:00
* < / h l j s >
*
* /
2014-11-03 17:32:25 +01:00
function mdIconDirective ( ) {
2014-09-30 12:29:53 +02:00
return {
restrict : 'E' ,
2014-11-03 17:32:25 +01:00
template : '<object class="md-icon"></object>' ,
2014-09-30 12:29:53 +02:00
compile : function ( element , attr ) {
var object = angular . element ( element [ 0 ] . children [ 0 ] ) ;
if ( angular . isDefined ( attr . icon ) ) {
object . attr ( 'data' , attr . icon ) ;
}
}
} ;
}
2014-11-03 17:32:25 +01:00
} ) ( ) ;
2014-09-30 12:29:53 +02:00
2014-11-03 17:32:25 +01:00
( function ( ) {
2014-09-30 12:29:53 +02:00
/ * *
* @ ngdoc module
* @ name material . components . list
* @ description
* List module
* /
angular . module ( 'material.components.list' , [ ] )
2014-11-03 17:32:25 +01:00
. directive ( 'mdList' , [
mdListDirective
2014-09-30 12:29:53 +02:00
] )
2014-11-03 17:32:25 +01:00
. directive ( 'mdItem' , [
mdItemDirective
2014-09-30 12:29:53 +02:00
] ) ;
/ * *
* @ ngdoc directive
2014-11-03 17:32:25 +01:00
* @ name mdList
2014-09-30 12:29:53 +02:00
* @ module material . components . list
*
* @ restrict E
*
* @ description
2014-11-03 17:32:25 +01:00
* The ` <md-list> ` directive is a list container for 1. . n ` <md-item> ` tags .
2014-09-30 12:29:53 +02:00
*
* @ usage
* < hljs lang = "html" >
2014-11-03 17:32:25 +01:00
* < md - list >
* < md - item ng - repeat = "item in todos" >
* < div class = "md-tile-left" >
2014-09-30 12:29:53 +02:00
* < img ng - src = "{{item.face}}" class = "face" alt = "{{item.who}}" >
* < / d i v >
2014-11-03 17:32:25 +01:00
* < div class = "md-tile-content" >
2014-10-12 19:07:47 +02:00
* < h3 > { { item . what } } < / h 3 >
* < h4 > { { item . who } } < / h 4 >
2014-09-30 12:29:53 +02:00
* < p >
* { { item . notes } }
* < / p >
* < / d i v >
*
2014-11-03 17:32:25 +01:00
* < / m d - i t e m >
* < / m d - l i s t >
2014-09-30 12:29:53 +02:00
* < / h l j s >
*
* /
2014-11-03 17:32:25 +01:00
function mdListDirective ( ) {
2014-09-30 12:29:53 +02:00
return {
restrict : 'E' ,
link : function ( $scope , $element , $attr ) {
$element . attr ( {
2014-10-12 19:07:47 +02:00
'role' : 'list'
2014-09-30 12:29:53 +02:00
} ) ;
}
} ;
}
/ * *
* @ ngdoc directive
2014-11-03 17:32:25 +01:00
* @ name mdItem
2014-09-30 12:29:53 +02:00
* @ module material . components . list
*
* @ restrict E
*
* @ description
2014-11-03 17:32:25 +01:00
* The ` <md-item> ` directive is a container intended for row items in a ` <md-list> ` container .
2014-09-30 12:29:53 +02:00
*
* @ usage
* < hljs lang = "html" >
2014-11-03 17:32:25 +01:00
* < md - list >
* < md - item >
2014-09-30 12:29:53 +02:00
* Item content in list
2014-11-03 17:32:25 +01:00
* < / m d - i t e m >
* < / m d - l i s t >
2014-09-30 12:29:53 +02:00
* < / h l j s >
*
* /
2014-11-03 17:32:25 +01:00
function mdItemDirective ( ) {
2014-09-30 12:29:53 +02:00
return {
restrict : 'E' ,
link : function ( $scope , $element , $attr ) {
$element . attr ( {
2014-10-12 19:07:47 +02:00
'role' : 'listitem'
2014-09-30 12:29:53 +02:00
} ) ;
}
} ;
}
2014-11-03 17:32:25 +01:00
} ) ( ) ;
2014-09-30 12:29:53 +02:00
2014-11-03 17:32:25 +01:00
( function ( ) {
2014-09-30 12:29:53 +02:00
/ * *
* @ ngdoc module
2014-11-03 17:32:25 +01:00
* @ name material . components . progressCircular
* @ description Circular Progress module !
2014-09-30 12:29:53 +02:00
* /
2014-11-03 17:32:25 +01:00
angular . module ( 'material.components.progressCircular' , [
2014-09-30 12:29:53 +02:00
'material.animations' ,
2014-11-06 16:55:16 +01:00
'material.services.aria' ,
'material.services.theming' ,
2014-09-30 12:29:53 +02:00
] )
2014-11-03 17:32:25 +01:00
. directive ( 'mdProgressCircular' , [
'$$rAF' ,
'$mdEffects' ,
2014-11-06 16:55:16 +01:00
'$mdTheming' ,
2014-11-03 17:32:25 +01:00
MdProgressCircularDirective
2014-09-30 12:29:53 +02:00
] ) ;
/ * *
* @ ngdoc directive
2014-11-03 17:32:25 +01:00
* @ name mdProgressCircular
* @ module material . components . progressCircular
2014-09-30 12:29:53 +02:00
* @ restrict E
*
2014-11-03 17:32:25 +01:00
* @ description
* The circular progress directive is used to make loading content in your app as delightful and painless as possible by minimizing the amount of visual change a user sees before they can view and interact with content .
2014-09-30 12:29:53 +02:00
*
2014-11-03 17:32:25 +01:00
* For operations where the percentage of the operation completed can be determined , use a determinate indicator . They give users a quick sense of how long an operation will take .
*
* For operations where the user is asked to wait a moment while something finishes up , and it ’ s not necessary to expose what ' s happening behind the scenes and how long it will take , use an indeterminate indicator .
*
* @ param { string } mode Select from one of two modes : determinate and indeterminate .
* @ param { number = } value In determinate mode , this number represents the percentage of the circular progress . Default : 0
* @ param { number = } diameter This specifies the diamter of the circular progress . Default : 48
2014-09-30 12:29:53 +02:00
*
* @ usage
* < hljs lang = "html" >
2014-11-03 17:32:25 +01:00
* < md - progress - circular mode = "determinate" value = "..." > < / m d - p r o g r e s s - c i r c u l a r >
2014-09-30 12:29:53 +02:00
*
2014-11-03 17:32:25 +01:00
* < md - progress - circular mode = "determinate" ng - value = "..." > < / m d - p r o g r e s s - c i r c u l a r >
2014-09-30 12:29:53 +02:00
*
2014-11-03 17:32:25 +01:00
* < md - progress - circular mode = "determinate" value = "..." diameter = "100" > < / m d - p r o g r e s s - c i r c u l a r >
2014-09-30 12:29:53 +02:00
*
2014-11-03 17:32:25 +01:00
* < md - progress - circular mode = "indeterminate" > < / m d - p r o g r e s s - c i r c u l a r >
2014-09-30 12:29:53 +02:00
* < / h l j s >
* /
2014-11-06 16:55:16 +01:00
function MdProgressCircularDirective ( $$rAF , $mdEffects , $mdTheming ) {
2014-11-03 17:32:25 +01:00
var fillRotations = new Array ( 101 ) ,
fixRotations = new Array ( 101 ) ;
for ( var i = 0 ; i < 101 ; i ++ ) {
var percent = i / 100 ;
var rotation = Math . floor ( percent * 180 ) ;
fillRotations [ i ] = 'rotate(' + rotation . toString ( ) + 'deg)' ;
fixRotations [ i ] = 'rotate(' + ( rotation * 2 ) . toString ( ) + 'deg)' ;
}
2014-09-30 12:29:53 +02:00
return {
restrict : 'E' ,
2014-11-03 17:32:25 +01:00
template :
2014-11-06 16:55:16 +01:00
'<div class="md-wrapper1"><div class="md-wrapper2"><div class="md-circle">' +
'<div class="md-mask md-full">' +
'<div class="md-fill"></div>' +
2014-11-03 17:32:25 +01:00
'</div>' +
2014-11-06 16:55:16 +01:00
'<div class="md-mask md-half">' +
'<div class="md-fill"></div>' +
'<div class="md-fill md-fix"></div>' +
2014-11-03 17:32:25 +01:00
'</div>' +
2014-11-06 16:55:16 +01:00
'<div class="md-shadow"></div>' +
2014-11-03 17:32:25 +01:00
'</div>' +
2014-11-06 16:55:16 +01:00
'<div class="md-inset"></div></div></div>' ,
2014-11-03 17:32:25 +01:00
compile : compile
2014-09-30 12:29:53 +02:00
} ;
2014-11-03 17:32:25 +01:00
function compile ( tElement , tAttrs , transclude ) {
tElement . attr ( 'aria-valuemin' , 0 ) ;
tElement . attr ( 'aria-valuemax' , 100 ) ;
tElement . attr ( 'role' , 'progressbar' ) ;
2014-09-30 12:29:53 +02:00
2014-11-03 17:32:25 +01:00
return postLink ;
}
2014-09-30 12:29:53 +02:00
2014-11-03 17:32:25 +01:00
function postLink ( scope , element , attr ) {
2014-11-06 16:55:16 +01:00
$mdTheming ( element ) ;
2014-11-03 17:32:25 +01:00
var circle = element [ 0 ] ,
2014-11-06 16:55:16 +01:00
fill = circle . querySelectorAll ( '.md-fill, .md-mask.md-full' ) ,
fix = circle . querySelectorAll ( '.md-fill.md-fix' ) ,
2014-11-03 17:32:25 +01:00
i , clamped , fillRotation , fixRotation ;
2014-09-30 12:29:53 +02:00
2014-11-03 17:32:25 +01:00
var diameter = attr . diameter || 48 ;
var scale = diameter / 48 ;
2014-09-30 12:29:53 +02:00
2014-11-03 17:32:25 +01:00
circle . style [ $mdEffects . TRANSFORM ] = 'scale(' + scale . toString ( ) + ')' ;
2014-09-30 12:29:53 +02:00
2014-11-03 17:32:25 +01:00
attr . $observe ( 'value' , function ( value ) {
clamped = clamp ( value ) ;
fillRotation = fillRotations [ clamped ] ;
fixRotation = fixRotations [ clamped ] ;
element . attr ( 'aria-valuenow' , clamped ) ;
for ( i = 0 ; i < fill . length ; i ++ ) {
fill [ i ] . style [ $mdEffects . TRANSFORM ] = fillRotation ;
2014-09-30 12:29:53 +02:00
}
2014-11-03 17:32:25 +01:00
for ( i = 0 ; i < fix . length ; i ++ ) {
fix [ i ] . style [ $mdEffects . TRANSFORM ] = fixRotation ;
}
} ) ;
2014-09-30 12:29:53 +02:00
}
2014-11-03 17:32:25 +01:00
function clamp ( value ) {
if ( value > 100 ) {
return 100 ;
2014-09-30 12:29:53 +02:00
}
2014-11-03 17:32:25 +01:00
if ( value < 0 ) {
return 0 ;
}
return Math . ceil ( value || 0 ) ;
}
2014-09-30 12:29:53 +02:00
}
2014-11-03 17:32:25 +01:00
} ) ( ) ;
( function ( ) {
/ * *
* @ ngdoc module
* @ name material . components . progressLinear
* @ description Linear Progress module !
* /
angular . module ( 'material.components.progressLinear' , [
'material.animations' ,
2014-11-06 16:55:16 +01:00
'material.services.theming' ,
2014-11-03 17:32:25 +01:00
'material.services.aria'
] )
. directive ( 'mdProgressLinear' , [
'$$rAF' ,
'$mdEffects' ,
2014-11-06 16:55:16 +01:00
'$mdTheming' ,
2014-11-03 17:32:25 +01:00
MdProgressLinearDirective
] ) ;
2014-09-30 12:29:53 +02:00
/ * *
* @ ngdoc directive
2014-11-03 17:32:25 +01:00
* @ name mdProgressLinear
* @ module material . components . progressLinear
2014-09-30 12:29:53 +02:00
* @ restrict E
*
* @ description
2014-11-03 17:32:25 +01:00
* The linear progress directive is used to make loading content in your app as delightful and painless as possible by minimizing the amount of visual change a user sees before they can view and interact with content . Each operation should only be represented by one activity indicator — for example , one refresh operation should not display both a refresh bar and an activity circle .
2014-09-30 12:29:53 +02:00
*
2014-11-03 17:32:25 +01:00
* For operations where the percentage of the operation completed can be determined , use a determinate indicator . They give users a quick sense of how long an operation will take .
2014-09-30 12:29:53 +02:00
*
2014-11-03 17:32:25 +01:00
* For operations where the user is asked to wait a moment while something finishes up , and it ’ s not necessary to expose what ' s happening behind the scenes and how long it will take , use an indeterminate indicator .
*
* @ param { string } mode Select from one of four modes : determinate , indeterminate , buffer or query .
* @ param { number = } value In determinate and buffer modes , this number represents the percentage of the primary progress bar . Default : 0
* @ param { number = } secondaryValue In the buffer mode , this number represents the precentage of the secondary progress bar . Default : 0
2014-09-30 12:29:53 +02:00
*
* @ usage
* < hljs lang = "html" >
2014-11-03 17:32:25 +01:00
* < md - progress - linear mode = "determinate" value = "..." > < / m d - p r o g r e s s - l i n e a r >
2014-09-30 12:29:53 +02:00
*
2014-11-03 17:32:25 +01:00
* < md - progress - linear mode = "determinate" ng - value = "..." > < / m d - p r o g r e s s - l i n e a r >
2014-09-30 12:29:53 +02:00
*
2014-11-03 17:32:25 +01:00
* < md - progress - linear mode = "indeterminate" > < / m d - p r o g r e s s - l i n e a r >
2014-09-30 12:29:53 +02:00
*
2014-11-03 17:32:25 +01:00
* < md - progress - linear mode = "buffer" value = "..." secondaryValue = "..." > < / m d - p r o g r e s s - l i n e a r >
*
* < md - progress - linear mode = "query" > < / m d - p r o g r e s s - l i n e a r >
* < / h l j s >
* /
2014-11-06 16:55:16 +01:00
function MdProgressLinearDirective ( $$rAF , $mdEffects , $mdTheming ) {
2014-11-03 17:32:25 +01:00
return {
restrict : 'E' ,
2014-11-06 16:55:16 +01:00
template : '<div class="md-container">' +
'<div class="md-dashed"></div>' +
'<div class="md-bar md-bar1"></div>' +
'<div class="md-bar md-bar2"></div>' +
2014-11-03 17:32:25 +01:00
'</div>' ,
compile : compile
} ;
function compile ( tElement , tAttrs , transclude ) {
tElement . attr ( 'aria-valuemin' , 0 ) ;
tElement . attr ( 'aria-valuemax' , 100 ) ;
tElement . attr ( 'role' , 'progressbar' ) ;
return postLink ;
}
function postLink ( scope , element , attr ) {
2014-11-06 16:55:16 +01:00
$mdTheming ( element ) ;
var bar1Style = element [ 0 ] . querySelector ( '.md-bar1' ) . style ,
bar2Style = element [ 0 ] . querySelector ( '.md-bar2' ) . style ,
container = angular . element ( element [ 0 ] . querySelector ( '.md-container' ) ) ;
2014-11-03 17:32:25 +01:00
attr . $observe ( 'value' , function ( value ) {
if ( attr . mode == 'query' ) {
return ;
}
var clamped = clamp ( value ) ;
element . attr ( 'aria-valuenow' , clamped ) ;
bar2Style [ $mdEffects . TRANSFORM ] = progressLinearTransforms [ clamped ] ;
} ) ;
attr . $observe ( 'secondaryvalue' , function ( value ) {
bar1Style [ $mdEffects . TRANSFORM ] = progressLinearTransforms [ clamp ( value ) ] ;
} ) ;
$$rAF ( function ( ) {
2014-11-06 16:55:16 +01:00
container . addClass ( 'md-ready' ) ;
2014-11-03 17:32:25 +01:00
} ) ;
}
function clamp ( value ) {
if ( value > 100 ) {
return 100 ;
}
if ( value < 0 ) {
return 0 ;
}
return Math . ceil ( value || 0 ) ;
}
}
// **********************************************************
// Private Methods
// **********************************************************
var progressLinearTransforms = ( function ( ) {
var values = new Array ( 101 ) ;
for ( var i = 0 ; i < 101 ; i ++ ) {
values [ i ] = makeTransform ( i ) ;
}
return values ;
function makeTransform ( value ) {
var scale = value / 100 ;
var translateX = ( value - 100 ) / 2 ;
return 'translateX(' + translateX . toString ( ) + '%) scale(' + scale . toString ( ) + ', 1)' ;
}
} ) ( ) ;
} ) ( ) ;
( function ( ) {
/ * *
* @ ngdoc module
* @ name material . components . radioButton
* @ description radioButton module !
* /
angular . module ( 'material.components.radioButton' , [
'material.core' ,
'material.animations' ,
2014-11-06 16:55:16 +01:00
'material.services.aria' ,
'material.services.theming'
2014-11-03 17:32:25 +01:00
] )
. directive ( 'mdRadioGroup' , [
'$mdUtil' ,
'$mdConstant' ,
2014-11-06 16:55:16 +01:00
'$mdTheming' ,
2014-11-03 17:32:25 +01:00
mdRadioGroupDirective
] )
. directive ( 'mdRadioButton' , [
'$mdAria' ,
'$mdUtil' ,
2014-11-06 16:55:16 +01:00
'$mdTheming' ,
2014-11-03 17:32:25 +01:00
mdRadioButtonDirective
] ) ;
/ * *
* @ ngdoc directive
* @ module material . components . radioButton
* @ name mdRadioGroup
*
* @ restrict E
*
* @ description
* The ` <md-radio-group> ` directive identifies a grouping
* container for the 1. . n grouped radio buttons ; specified using nested
* ` <md-radio-button> ` tags .
*
* @ param { string } ngModel Assignable angular expression to data - bind to .
* @ param { boolean = } noink Use of attribute indicates flag to disable ink ripple effects .
*
* @ usage
* < hljs lang = "html" >
* < md - radio - group ng - model = "selected" >
*
* < md - radio - button
* ng - repeat = "d in colorOptions"
* ng - value = "d.value" aria - label = "{{ d.label }}" >
*
* { { d . label } }
*
* < / m d - r a d i o - b u t t o n >
*
* < / m d - r a d i o - g r o u p >
* < / h l j s >
*
* /
2014-11-06 16:55:16 +01:00
function mdRadioGroupDirective ( $mdUtil , $mdConstant , $mdTheming ) {
2014-11-03 17:32:25 +01:00
RadioGroupController . prototype = createRadioGroupControllerProto ( ) ;
return {
restrict : 'E' ,
controller : [ '$element' , RadioGroupController ] ,
require : [ 'mdRadioGroup' , '?ngModel' ] ,
link : link
} ;
function link ( scope , element , attr , ctrls ) {
2014-11-06 16:55:16 +01:00
$mdTheming ( element ) ;
2014-11-03 17:32:25 +01:00
var rgCtrl = ctrls [ 0 ] ,
ngModelCtrl = ctrls [ 1 ] || {
$setViewValue : angular . noop
} ;
function keydownListener ( ev ) {
if ( ev . which === $mdConstant . KEY _CODE . LEFT _ARROW ) {
ev . preventDefault ( ) ;
rgCtrl . selectPrevious ( ) ;
}
else if ( ev . which === $mdConstant . KEY _CODE . RIGHT _ARROW ) {
ev . preventDefault ( ) ;
rgCtrl . selectNext ( ) ;
}
}
rgCtrl . init ( ngModelCtrl ) ;
element . attr ( {
'role' : 'radiogroup' ,
'tabIndex' : '0'
} )
. on ( 'keydown' , keydownListener ) ;
}
function RadioGroupController ( $element ) {
this . _radioButtonRenderFns = [ ] ;
this . $element = $element ;
}
function createRadioGroupControllerProto ( ) {
return {
init : function ( ngModelCtrl ) {
this . _ngModelCtrl = ngModelCtrl ;
this . _ngModelCtrl . $render = angular . bind ( this , this . render ) ;
} ,
add : function ( rbRender ) {
this . _radioButtonRenderFns . push ( rbRender ) ;
} ,
remove : function ( rbRender ) {
var index = this . _radioButtonRenderFns . indexOf ( rbRender ) ;
if ( index !== - 1 ) {
this . _radioButtonRenderFns . splice ( index , 1 ) ;
}
} ,
render : function ( ) {
this . _radioButtonRenderFns . forEach ( function ( rbRender ) {
rbRender ( ) ;
} ) ;
} ,
setViewValue : function ( value , eventType ) {
this . _ngModelCtrl . $setViewValue ( value , eventType ) ;
// update the other radio buttons as well
this . render ( ) ;
} ,
getViewValue : function ( ) {
return this . _ngModelCtrl . $viewValue ;
} ,
selectNext : function ( ) {
return changeSelectedButton ( this . $element , 1 ) ;
} ,
selectPrevious : function ( ) {
return changeSelectedButton ( this . $element , - 1 ) ;
} ,
setActiveDescendant : function ( radioId ) {
this . $element . attr ( 'aria-activedescendant' , radioId ) ;
}
} ;
}
/ * *
* Change the radio group ' s selected button by a given increment .
* If no button is selected , select the first button .
* /
function changeSelectedButton ( parent , increment ) {
// Coerce all child radio buttons into an array, then wrap then in an iterator
var buttons = $mdUtil . iterator (
Array . prototype . slice . call ( parent [ 0 ] . querySelectorAll ( 'md-radio-button' ) ) ,
true
) ;
if ( buttons . count ( ) ) {
var selected = parent [ 0 ] . querySelector ( 'md-radio-button.md-checked' ) ;
var target = buttons [ increment < 0 ? 'previous' : 'next' ] ( selected ) || buttons . first ( ) ;
// Activate radioButton's click listener (triggerHandler won't create a real click event)
angular . element ( target ) . triggerHandler ( 'click' ) ;
}
}
}
/ * *
* @ ngdoc directive
* @ module material . components . radioButton
* @ name mdRadioButton
*
* @ restrict E
*
* @ description
* The ` <md-radio-button> ` directive is the child directive required to be used within ` <md-radioo-group> ` elements .
*
* While similar to the ` <input type="radio" ng-model="" value=""> ` directive ,
* the ` <md-radio-button> ` directive provides ink effects , ARIA support , and
* supports use within named radio groups .
*
* @ param { string } ngModel Assignable angular expression to data - bind to .
* @ param { string = } ngChange Angular expression to be executed when input changes due to user
* interaction with the input element .
* @ param { string } ngValue Angular expression which sets the value to which the expression should
* be set when selected . *
* @ param { string } value The value to which the expression should be set when selected .
* @ param { string = } name Property name of the form under which the control is published .
* @ param { string = } ariaLabel Publish the button label used by screen - readers for accessibility . Defaults to the radio button ' s text .
*
* @ usage
* < hljs lang = "html" >
*
* < md - radio - button value = "1" aria - label = "Label 1" >
* Label 1
* < / m d - r a d i o - b u t t o n >
*
* < md - radio - button ng - model = "color" ng - value = "specialValue" aria - label = "Green" >
* Green
* < / m d - r a d i o - b u t t o n >
*
* < / h l j s >
2014-09-30 12:29:53 +02:00
*
* /
2014-11-06 16:55:16 +01:00
function mdRadioButtonDirective ( $mdAria , $mdUtil , $mdTheming ) {
2014-09-30 12:29:53 +02:00
2014-11-03 17:32:25 +01:00
var CHECKED _CSS = 'md-checked' ;
2014-09-30 12:29:53 +02:00
return {
restrict : 'E' ,
2014-11-03 17:32:25 +01:00
require : '^mdRadioGroup' ,
2014-09-30 12:29:53 +02:00
transclude : true ,
2014-11-03 17:32:25 +01:00
template : '<div class="md-container" ink-ripple="checkbox">' +
'<div class="md-off"></div>' +
'<div class="md-on"></div>' +
2014-09-30 12:29:53 +02:00
'</div>' +
2014-11-03 17:32:25 +01:00
'<div ng-transclude class="md-label"></div>' ,
2014-09-30 12:29:53 +02:00
link : link
} ;
function link ( scope , element , attr , rgCtrl ) {
var lastChecked ;
2014-11-06 16:55:16 +01:00
$mdTheming ( element ) ;
2014-10-12 19:07:47 +02:00
configureAria ( element , scope ) ;
2014-09-30 12:29:53 +02:00
rgCtrl . add ( render ) ;
attr . $observe ( 'value' , render ) ;
element
. on ( 'click' , listener )
. on ( '$destroy' , function ( ) {
rgCtrl . remove ( render ) ;
2014-10-12 19:07:47 +02:00
} ) ;
2014-09-30 12:29:53 +02:00
function listener ( ev ) {
if ( element [ 0 ] . hasAttribute ( 'disabled' ) ) return ;
scope . $apply ( function ( ) {
rgCtrl . setViewValue ( attr . value , ev && ev . type ) ;
} ) ;
}
function render ( ) {
var checked = ( rgCtrl . getViewValue ( ) === attr . value ) ;
if ( checked === lastChecked ) {
return ;
}
lastChecked = checked ;
2014-10-12 19:07:47 +02:00
element . attr ( 'aria-checked' , checked ) ;
2014-09-30 12:29:53 +02:00
if ( checked ) {
element . addClass ( CHECKED _CSS ) ;
2014-10-12 19:07:47 +02:00
rgCtrl . setActiveDescendant ( element . attr ( 'id' ) ) ;
2014-09-30 12:29:53 +02:00
} else {
element . removeClass ( CHECKED _CSS ) ;
}
}
2014-10-12 19:07:47 +02:00
/ * *
* Inject ARIA - specific attributes appropriate for each radio button
* /
function configureAria ( element , scope ) {
scope . ariaId = buildAriaID ( ) ;
element . attr ( {
'id' : scope . ariaId ,
'role' : 'radio' ,
'aria-checked' : 'false'
} ) ;
2014-11-06 16:55:16 +01:00
$mdAria . expect ( element , 'aria-label' , true ) ;
2014-10-12 19:07:47 +02:00
/ * *
* Build a unique ID for each radio button that will be used with aria - activedescendant .
* Preserve existing ID if already specified .
* @ returns { * | string }
* /
function buildAriaID ( ) {
2014-11-03 17:32:25 +01:00
return attr . id || ( 'radio' + "_" + $mdUtil . nextUid ( ) ) ;
2014-10-12 19:07:47 +02:00
}
}
2014-09-30 12:29:53 +02:00
}
}
2014-11-03 17:32:25 +01:00
} ) ( ) ;
2014-09-30 12:29:53 +02:00
2014-11-03 17:32:25 +01:00
( function ( ) {
2014-09-30 12:29:53 +02:00
/ * *
* @ ngdoc module
* @ name material . components . sidenav
*
* @ description
* A Sidenav QP component .
* /
angular . module ( 'material.components.sidenav' , [
2014-11-03 17:32:25 +01:00
'material.core' ,
2014-10-12 19:07:47 +02:00
'material.services.registry' ,
2014-11-06 16:55:16 +01:00
'material.services.media' ,
'material.components.backdrop' ,
'material.services.theming' ,
2014-10-12 19:07:47 +02:00
'material.animations'
2014-09-30 12:29:53 +02:00
] )
2014-11-03 17:32:25 +01:00
. factory ( '$mdSidenav' , [
'$mdComponentRegistry' ,
mdSidenavService
2014-09-30 12:29:53 +02:00
] )
2014-11-03 17:32:25 +01:00
. directive ( 'mdSidenav' , [
2014-09-30 12:29:53 +02:00
'$timeout' ,
2014-11-06 16:55:16 +01:00
'$animate' ,
'$parse' ,
'$mdMedia' ,
2014-11-03 17:32:25 +01:00
'$mdConstant' ,
2014-11-06 16:55:16 +01:00
'$compile' ,
'$mdTheming' ,
2014-11-03 17:32:25 +01:00
mdSidenavDirective
2014-09-30 12:29:53 +02:00
] )
2014-11-03 17:32:25 +01:00
. controller ( '$mdSidenavController' , [
2014-09-30 12:29:53 +02:00
'$scope' ,
'$element' ,
'$attrs' ,
'$timeout' ,
2014-11-03 17:32:25 +01:00
'$mdSidenav' ,
'$mdComponentRegistry' ,
mdSidenavController
2014-09-30 12:29:53 +02:00
] ) ;
2014-11-03 17:32:25 +01:00
/ *
2014-09-30 12:29:53 +02:00
* @ private
* @ ngdoc object
2014-11-03 17:32:25 +01:00
* @ name mdSidenavController
2014-09-30 12:29:53 +02:00
* @ module material . components . sidenav
*
* @ description
2014-11-03 17:32:25 +01:00
* The controller for mdSidenav components .
2014-09-30 12:29:53 +02:00
* /
2014-11-03 17:32:25 +01:00
function mdSidenavController ( $scope , $element , $attrs , $timeout , $mdSidenav , $mdComponentRegistry ) {
2014-09-30 12:29:53 +02:00
var self = this ;
2014-11-03 17:32:25 +01:00
$mdComponentRegistry . register ( this , $attrs . componentId ) ;
2014-09-30 12:29:53 +02:00
this . isOpen = function ( ) {
return ! ! $scope . isOpen ;
} ;
this . toggle = function ( ) {
$scope . isOpen = ! $scope . isOpen ;
} ;
this . open = function ( ) {
$scope . isOpen = true ;
} ;
this . close = function ( ) {
$scope . isOpen = false ;
} ;
}
2014-11-03 17:32:25 +01:00
/ *
2014-09-30 12:29:53 +02:00
* @ private
* @ ngdoc service
2014-11-03 17:32:25 +01:00
* @ name $mdSidenav
2014-09-30 12:29:53 +02:00
* @ module material . components . sidenav
*
* @ description
2014-11-03 17:32:25 +01:00
* $mdSidenav makes it easy to interact with multiple sidenavs
2014-09-30 12:29:53 +02:00
* in an app .
*
* @ usage
*
* ` ` ` javascript
* // Toggle the given sidenav
2014-11-03 17:32:25 +01:00
* $mdSidenav ( componentId ) . toggle ( ) ;
2014-09-30 12:29:53 +02:00
*
* // Open the given sidenav
2014-11-03 17:32:25 +01:00
* $mdSidenav ( componentId ) . open ( ) ;
2014-09-30 12:29:53 +02:00
*
* // Close the given sidenav
2014-11-03 17:32:25 +01:00
* $mdSidenav ( componentId ) . close ( ) ;
2014-09-30 12:29:53 +02:00
* ` ` `
* /
2014-11-03 17:32:25 +01:00
function mdSidenavService ( $mdComponentRegistry ) {
2014-09-30 12:29:53 +02:00
return function ( handle ) {
2014-11-03 17:32:25 +01:00
var instance = $mdComponentRegistry . get ( handle ) ;
2014-09-30 12:29:53 +02:00
if ( ! instance ) {
2014-11-03 17:32:25 +01:00
$mdComponentRegistry . notFoundError ( handle ) ;
2014-09-30 12:29:53 +02:00
}
return {
isOpen : function ( ) {
2014-11-06 16:55:16 +01:00
return instance && instance . isOpen ( ) ;
2014-09-30 12:29:53 +02:00
} ,
toggle : function ( ) {
2014-11-06 16:55:16 +01:00
instance && instance . toggle ( ) ;
2014-09-30 12:29:53 +02:00
} ,
2014-11-06 16:55:16 +01:00
open : function ( ) {
instance && instance . open ( ) ;
2014-09-30 12:29:53 +02:00
} ,
2014-11-06 16:55:16 +01:00
close : function ( ) {
instance && instance . close ( ) ;
2014-09-30 12:29:53 +02:00
}
} ;
} ;
}
/ * *
* @ ngdoc directive
2014-11-03 17:32:25 +01:00
* @ name mdSidenav
2014-09-30 12:29:53 +02:00
* @ module material . components . sidenav
* @ restrict E
*
* @ description
*
* A Sidenav component that can be opened and closed programatically .
*
2014-11-06 16:55:16 +01:00
* By default , upon opening it will slide out on top of the main content area .
2014-09-30 12:29:53 +02:00
*
* @ usage
* < hljs lang = "html" >
* < div layout = "horizontal" ng - controller = "MyController" >
2014-11-03 17:32:25 +01:00
* < md - sidenav component - id = "left" class = "md-sidenav-left" >
2014-09-30 12:29:53 +02:00
* Left Nav !
2014-11-03 17:32:25 +01:00
* < / m d - s i d e n a v >
2014-09-30 12:29:53 +02:00
*
2014-11-03 17:32:25 +01:00
* < md - content >
2014-09-30 12:29:53 +02:00
* Center Content
2014-11-03 17:32:25 +01:00
* < md - button ng - click = "openLeftMenu()" >
2014-09-30 12:29:53 +02:00
* Open Left Menu
2014-11-03 17:32:25 +01:00
* < / m d - b u t t o n >
* < / m d - c o n t e n t >
2014-09-30 12:29:53 +02:00
*
2014-11-06 16:55:16 +01:00
* < md - sidenav component - id = "right"
* is - locked - open = "$media('min-width: 333px')"
* class = "md-sidenav-right" >
2014-09-30 12:29:53 +02:00
* Right Nav !
2014-11-03 17:32:25 +01:00
* < / m d - s i d e n a v >
2014-09-30 12:29:53 +02:00
* < / d i v >
* < / h l j s >
*
* < hljs lang = "js" >
* var app = angular . module ( 'myApp' , [ 'ngMaterial' ] ) ;
2014-11-03 17:32:25 +01:00
* app . controller ( 'MyController' , function ( $scope , $mdSidenav ) {
2014-09-30 12:29:53 +02:00
* $scope . openLeftMenu = function ( ) {
2014-11-03 17:32:25 +01:00
* $mdSidenav ( 'left' ) . toggle ( ) ;
2014-09-30 12:29:53 +02:00
* } ;
* } ) ;
* < / h l j s >
2014-11-06 16:55:16 +01:00
*
* @ param { expression = } is - open A model bound to whether the sidenav is opened .
* @ param { string = } component - id componentId to use with $mdSidenav service .
* @ param { expression = } is - locked - open When this expression evalutes to true ,
* the sidenav 'locks open' : it falls into the content ' s flow instead
* of appearing over it . This overrides the ` is-open ` attribute .
*
* A $media ( ) function is exposed to the is - locked - open attribute , which
* can be given a media query or one of the ` sm ` , ` md ` or ` lg ` presets .
* Examples :
*
* - ` <md-sidenav is-locked-open="shouldLockOpen"></md-sidenav> `
* - ` <md-sidenav is-locked-open=" $ media('min-width: 1000px')"></md-sidenav> `
* - ` <md-sidenav is-locked-open=" $ media('sm')"></md-sidenav> ` <!-- locks open on small screens ! -- >
2014-09-30 12:29:53 +02:00
* /
2014-11-06 16:55:16 +01:00
function mdSidenavDirective ( $timeout , $animate , $parse , $mdMedia , $mdConstant , $compile , $mdTheming ) {
2014-09-30 12:29:53 +02:00
return {
restrict : 'E' ,
2014-11-06 16:55:16 +01:00
scope : {
isOpen : '=?'
} ,
2014-11-03 17:32:25 +01:00
controller : '$mdSidenavController' ,
2014-11-06 16:55:16 +01:00
compile : function ( element ) {
element . addClass ( 'md-closed' ) ;
element . attr ( 'tabIndex' , '-1' ) ;
return postLink ;
}
2014-10-12 19:07:47 +02:00
} ;
2014-09-30 12:29:53 +02:00
2014-10-12 19:07:47 +02:00
function postLink ( scope , element , attr , sidenavCtrl ) {
2014-11-06 16:55:16 +01:00
var isLockedOpenParsed = $parse ( attr . isLockedOpen ) ;
var backdrop = $compile (
'<md-backdrop class="md-sidenav-backdrop md-opaque">'
) ( scope ) ;
$mdTheming . inherit ( backdrop , element ) ;
2014-09-30 12:29:53 +02:00
2014-11-06 16:55:16 +01:00
scope . $watch ( 'isOpen' , setOpen ) ;
scope . $watch ( function ( ) {
return isLockedOpenParsed ( scope . $parent , {
$media : $mdMedia
} ) ;
} , function ( isLocked ) {
element . toggleClass ( 'md-locked-open' , ! ! isLocked ) ;
backdrop . toggleClass ( 'md-locked-open' , ! ! isLocked ) ;
} ) ;
2014-09-30 12:29:53 +02:00
2014-10-12 19:07:47 +02:00
/ * *
* Toggle the SideNav view and attach / detach listeners
* @ param isOpen
* /
2014-11-06 16:55:16 +01:00
function setOpen ( isOpen ) {
2014-10-12 19:07:47 +02:00
var parent = element . parent ( ) ;
2014-09-30 12:29:53 +02:00
2014-11-06 16:55:16 +01:00
parent [ isOpen ? 'on' : 'off' ] ( 'keydown' , onKeyDown ) ;
$animate [ isOpen ? 'enter' : 'leave' ] ( backdrop , parent ) ;
backdrop [ isOpen ? 'on' : 'off' ] ( 'click' , close ) ;
2014-09-30 12:29:53 +02:00
2014-11-06 16:55:16 +01:00
$animate [ isOpen ? 'removeClass' : 'addClass' ] ( element , 'md-closed' ) . then ( function ( ) {
// If we opened, and haven't closed again before the animation finished
if ( scope . isOpen ) {
element . focus ( ) ;
}
2014-10-12 19:07:47 +02:00
} ) ;
}
/ * *
* Auto - close sideNav when the ` escape ` key is pressed .
* @ param evt
* /
2014-11-06 16:55:16 +01:00
function onKeyDown ( ev ) {
if ( ev . which === $mdConstant . KEY _CODE . ESCAPE ) {
2014-10-12 19:07:47 +02:00
close ( ) ;
2014-11-06 16:55:16 +01:00
ev . preventDefault ( ) ;
ev . stopPropagation ( ) ;
2014-09-30 12:29:53 +02:00
}
2014-10-12 19:07:47 +02:00
}
/ * *
* With backdrop ` clicks ` or ` escape ` key - press , immediately
* apply the CSS close transition ... Then notify the controller
* to close ( ) and perform its own actions .
* /
function close ( ) {
$timeout ( function ( ) {
sidenavCtrl . close ( ) ;
} ) ;
2014-09-30 12:29:53 +02:00
}
2014-10-12 19:07:47 +02:00
}
2014-09-30 12:29:53 +02:00
}
2014-11-03 17:32:25 +01:00
} ) ( ) ;
2014-09-30 12:29:53 +02:00
2014-11-03 17:32:25 +01:00
( function ( ) {
2014-09-30 12:29:53 +02:00
/ * *
* @ ngdoc module
* @ name material . components . slider
* /
angular . module ( 'material.components.slider' , [
2014-11-03 17:32:25 +01:00
'material.core' ,
2014-09-30 12:29:53 +02:00
'material.animations' ,
2014-11-06 16:55:16 +01:00
'material.services.aria' ,
'material.services.theming'
2014-09-30 12:29:53 +02:00
] )
2014-11-03 17:32:25 +01:00
. directive ( 'mdSlider' , [
2014-11-06 16:55:16 +01:00
'$mdTheming' ,
2014-09-30 12:29:53 +02:00
SliderDirective
] ) ;
/ * *
* @ ngdoc directive
2014-11-03 17:32:25 +01:00
* @ name mdSlider
2014-09-30 12:29:53 +02:00
* @ module material . components . slider
* @ restrict E
* @ description
2014-11-03 17:32:25 +01:00
* The ` <md-slider> ` component allows the user to choose from a range of
2014-09-30 12:29:53 +02:00
* values .
*
* It has two modes : 'normal' mode , where the user slides between a wide range
* of values , and 'discrete' mode , where the user slides between only a few
* select values .
*
* To enable discrete mode , add the ` discrete ` attribute to a slider ,
* and use the ` step ` attribute to change the distance between
* values the user is allowed to pick .
*
* @ usage
* < h4 > Normal Mode < / h 4 >
* < hljs lang = "html" >
2014-11-03 17:32:25 +01:00
* < md - slider ng - model = "myValue" min = "5" max = "500" >
* < / m d - s l i d e r >
2014-09-30 12:29:53 +02:00
* < / h l j s >
* < h4 > Discrete Mode < / h 4 >
* < hljs lang = "html" >
2014-11-03 17:32:25 +01:00
* < md - slider discrete ng - model = "myDiscreteValue" step = "10" min = "10" max = "130" >
* < / m d - s l i d e r >
2014-09-30 12:29:53 +02:00
* < / h l j s >
*
* @ param { boolean = } discrete Whether to enable discrete mode .
* @ param { number = } step The distance between values the user is allowed to pick . Default 1.
* @ param { number = } min The minimum value the user is allowed to pick . Default 0.
* @ param { number = } max The maximum value the user is allowed to pick . Default 100.
* /
2014-11-06 16:55:16 +01:00
function SliderDirective ( $mdTheming ) {
2014-09-30 12:29:53 +02:00
return {
scope : { } ,
2014-11-03 17:32:25 +01:00
require : [ '?ngModel' , 'mdSlider' ] ,
2014-09-30 12:29:53 +02:00
controller : [
'$scope' ,
'$element' ,
'$attrs' ,
'$$rAF' ,
'$window' ,
2014-11-03 17:32:25 +01:00
'$mdEffects' ,
'$mdAria' ,
'$mdUtil' ,
'$mdConstant' ,
2014-09-30 12:29:53 +02:00
SliderController
] ,
template :
2014-11-06 16:55:16 +01:00
'<div class="md-track-container">' +
'<div class="md-track"></div>' +
'<div class="md-track md-track-fill"></div>' +
'<div class="md-track-ticks"></div>' +
2014-09-30 12:29:53 +02:00
'</div>' +
2014-11-06 16:55:16 +01:00
'<div class="md-thumb-container">' +
'<div class="md-thumb"></div>' +
'<div class="md-focus-thumb"></div>' +
'<div class="md-focus-ring"></div>' +
'<div class="md-sign">' +
'<span class="md-thumb-text" ng-bind="modelValue"></span>' +
2014-09-30 12:29:53 +02:00
'</div>' +
2014-11-06 16:55:16 +01:00
'<div class="md-disabled-thumb"></div>' +
2014-09-30 12:29:53 +02:00
'</div>' ,
link : postLink
} ;
function postLink ( scope , element , attr , ctrls ) {
2014-11-06 16:55:16 +01:00
$mdTheming ( element ) ;
2014-09-30 12:29:53 +02:00
var ngModelCtrl = ctrls [ 0 ] || {
// Mock ngModelController if it doesn't exist to give us
// the minimum functionality needed
$setViewValue : function ( val ) {
this . $viewValue = val ;
this . $viewChangeListeners . forEach ( function ( cb ) { cb ( ) ; } ) ;
} ,
$parsers : [ ] ,
$formatters : [ ] ,
$viewChangeListeners : [ ]
} ;
var sliderCtrl = ctrls [ 1 ] ;
sliderCtrl . init ( ngModelCtrl ) ;
}
}
/ * *
* We use a controller for all the logic so that we can expose a few
* things to unit tests
* /
2014-11-03 17:32:25 +01:00
function SliderController ( scope , element , attr , $$rAF , $window , $mdEffects , $mdAria , $mdUtil , $mdConstant ) {
2014-09-30 12:29:53 +02:00
this . init = function init ( ngModelCtrl ) {
2014-11-06 16:55:16 +01:00
var thumb = angular . element ( element [ 0 ] . querySelector ( '.md-thumb' ) ) ;
2014-09-30 12:29:53 +02:00
var thumbContainer = thumb . parent ( ) ;
2014-11-06 16:55:16 +01:00
var trackContainer = angular . element ( element [ 0 ] . querySelector ( '.md-track-container' ) ) ;
var activeTrack = angular . element ( element [ 0 ] . querySelector ( '.md-track-fill' ) ) ;
var tickContainer = angular . element ( element [ 0 ] . querySelector ( '.md-track-ticks' ) ) ;
var throttledRefreshDimensions = $mdUtil . throttle ( refreshSliderDimensions , 5000 ) ;
2014-09-30 12:29:53 +02:00
// Default values, overridable by attrs
attr . min ? attr . $observe ( 'min' , updateMin ) : updateMin ( 0 ) ;
attr . max ? attr . $observe ( 'max' , updateMax ) : updateMax ( 100 ) ;
attr . step ? attr . $observe ( 'step' , updateStep ) : updateStep ( 1 ) ;
// We have to manually stop the $watch on ngDisabled because it exists
// on the parent scope, and won't be automatically destroyed when
// the component is destroyed.
var stopDisabledWatch = angular . noop ;
if ( attr . ngDisabled ) {
stopDisabledWatch = scope . $parent . $watch ( attr . ngDisabled , updateAriaDisabled ) ;
} else {
updateAriaDisabled ( ! ! attr . disabled ) ;
}
2014-11-06 16:55:16 +01:00
$mdAria . expect ( element , 'aria-label' , false ) ;
2014-09-30 12:29:53 +02:00
element . attr ( 'tabIndex' , 0 ) ;
2014-10-12 19:07:47 +02:00
element . attr ( 'role' , 'slider' ) ;
2014-09-30 12:29:53 +02:00
element . on ( 'keydown' , keydownListener ) ;
var hammertime = new Hammer ( element [ 0 ] , {
recognizers : [
[ Hammer . Pan , { direction : Hammer . DIRECTION _HORIZONTAL } ]
]
} ) ;
hammertime . on ( 'hammer.input' , onInput ) ;
hammertime . on ( 'panstart' , onPanStart ) ;
hammertime . on ( 'pan' , onPan ) ;
2014-10-12 19:07:47 +02:00
hammertime . on ( 'panend' , onPanEnd ) ;
2014-09-30 12:29:53 +02:00
// On resize, recalculate the slider's dimensions and re-render
2014-11-03 17:32:25 +01:00
function updateAll ( ) {
2014-09-30 12:29:53 +02:00
refreshSliderDimensions ( ) ;
ngModelRender ( ) ;
redrawTicks ( ) ;
2014-11-03 17:32:25 +01:00
}
setTimeout ( updateAll ) ;
var debouncedUpdateAll = $$rAF . debounce ( updateAll ) ;
angular . element ( $window ) . on ( 'resize' , debouncedUpdateAll ) ;
2014-09-30 12:29:53 +02:00
scope . $on ( '$destroy' , function ( ) {
2014-11-03 17:32:25 +01:00
angular . element ( $window ) . off ( 'resize' , debouncedUpdateAll ) ;
2014-09-30 12:29:53 +02:00
hammertime . destroy ( ) ;
stopDisabledWatch ( ) ;
} ) ;
ngModelCtrl . $render = ngModelRender ;
ngModelCtrl . $viewChangeListeners . push ( ngModelRender ) ;
ngModelCtrl . $formatters . push ( minMaxValidator ) ;
ngModelCtrl . $formatters . push ( stepValidator ) ;
/ * *
* Attributes
* /
var min ;
var max ;
var step ;
function updateMin ( value ) {
min = parseFloat ( value ) ;
element . attr ( 'aria-valuemin' , value ) ;
}
function updateMax ( value ) {
max = parseFloat ( value ) ;
element . attr ( 'aria-valuemax' , value ) ;
}
function updateStep ( value ) {
step = parseFloat ( value ) ;
redrawTicks ( ) ;
}
function updateAriaDisabled ( isDisabled ) {
element . attr ( 'aria-disabled' , ! ! isDisabled ) ;
}
// Draw the ticks with canvas.
// The alternative to drawing ticks with canvas is to draw one element for each tick,
// which could quickly become a performance bottleneck.
var tickCanvas , tickCtx ;
function redrawTicks ( ) {
if ( ! angular . isDefined ( attr . discrete ) ) return ;
var numSteps = Math . floor ( ( max - min ) / step ) ;
if ( ! tickCanvas ) {
tickCanvas = angular . element ( '<canvas style="position:absolute;">' ) ;
tickCtx = tickCanvas [ 0 ] . getContext ( '2d' ) ;
tickCtx . fillStyle = 'black' ;
tickContainer . append ( tickCanvas ) ;
}
var dimensions = getSliderDimensions ( ) ;
tickCanvas [ 0 ] . width = dimensions . width ;
tickCanvas [ 0 ] . height = dimensions . height ;
var distance ;
for ( var i = 0 ; i <= numSteps ; i ++ ) {
distance = Math . floor ( dimensions . width * ( i / numSteps ) ) ;
tickCtx . fillRect ( distance - 1 , 0 , 2 , dimensions . height ) ;
}
}
/ * *
* Refreshing Dimensions
* /
var sliderDimensions = { } ;
refreshSliderDimensions ( ) ;
function refreshSliderDimensions ( ) {
sliderDimensions = trackContainer [ 0 ] . getBoundingClientRect ( ) ;
}
function getSliderDimensions ( ) {
2014-10-12 19:07:47 +02:00
throttledRefreshDimensions ( ) ;
2014-09-30 12:29:53 +02:00
return sliderDimensions ;
}
/ * *
* left / right arrow listener
* /
function keydownListener ( ev ) {
2014-11-03 17:32:25 +01:00
if ( element [ 0 ] . hasAttribute ( 'disabled' ) ) {
return ;
}
2014-09-30 12:29:53 +02:00
var changeAmount ;
2014-11-03 17:32:25 +01:00
if ( ev . which === $mdConstant . KEY _CODE . LEFT _ARROW ) {
2014-09-30 12:29:53 +02:00
changeAmount = - step ;
2014-11-03 17:32:25 +01:00
} else if ( ev . which === $mdConstant . KEY _CODE . RIGHT _ARROW ) {
2014-09-30 12:29:53 +02:00
changeAmount = step ;
}
if ( changeAmount ) {
if ( ev . metaKey || ev . ctrlKey || ev . altKey ) {
changeAmount *= 4 ;
}
ev . preventDefault ( ) ;
ev . stopPropagation ( ) ;
scope . $evalAsync ( function ( ) {
setModelValue ( ngModelCtrl . $viewValue + changeAmount ) ;
} ) ;
}
}
/ * *
* ngModel setters and validators
* /
function setModelValue ( value ) {
ngModelCtrl . $setViewValue ( minMaxValidator ( stepValidator ( value ) ) ) ;
}
function ngModelRender ( ) {
2014-11-06 16:55:16 +01:00
if ( isNaN ( ngModelCtrl . $viewValue ) ) {
ngModelCtrl . $viewValue = ngModelCtrl . $modelValue ;
}
2014-09-30 12:29:53 +02:00
var percent = ( ngModelCtrl . $viewValue - min ) / ( max - min ) ;
scope . modelValue = ngModelCtrl . $viewValue ;
element . attr ( 'aria-valuenow' , ngModelCtrl . $viewValue ) ;
setSliderPercent ( percent ) ;
}
function minMaxValidator ( value ) {
if ( angular . isNumber ( value ) ) {
return Math . max ( min , Math . min ( max , value ) ) ;
}
}
function stepValidator ( value ) {
if ( angular . isNumber ( value ) ) {
return Math . round ( value / step ) * step ;
}
}
/ * *
* @ param percent 0 - 1
* /
function setSliderPercent ( percent ) {
activeTrack . css ( 'width' , ( percent * 100 ) + '%' ) ;
thumbContainer . css (
2014-11-03 17:32:25 +01:00
$mdEffects . TRANSFORM ,
2014-09-30 12:29:53 +02:00
'translate3d(' + getSliderDimensions ( ) . width * percent + 'px,0,0)'
) ;
2014-11-06 16:55:16 +01:00
element . toggleClass ( 'md-min' , percent === 0 ) ;
2014-09-30 12:29:53 +02:00
}
/ * *
* Slide listeners
* /
var isSliding = false ;
2014-10-12 19:07:47 +02:00
var isDiscrete = angular . isDefined ( attr . discrete ) ;
2014-09-30 12:29:53 +02:00
function onInput ( ev ) {
if ( ! isSliding && ev . eventType === Hammer . INPUT _START &&
! element [ 0 ] . hasAttribute ( 'disabled' ) ) {
isSliding = true ;
2014-10-12 19:07:47 +02:00
2014-09-30 12:29:53 +02:00
element . addClass ( 'active' ) ;
element [ 0 ] . focus ( ) ;
refreshSliderDimensions ( ) ;
2014-10-12 19:07:47 +02:00
onPan ( ev ) ;
ev . srcEvent . stopPropagation ( ) ;
2014-09-30 12:29:53 +02:00
} else if ( isSliding && ev . eventType === Hammer . INPUT _END ) {
2014-10-12 19:07:47 +02:00
2014-11-03 17:32:25 +01:00
if ( isSliding && isDiscrete ) onPanEnd ( ev ) ;
2014-09-30 12:29:53 +02:00
isSliding = false ;
2014-10-12 19:07:47 +02:00
2014-09-30 12:29:53 +02:00
element . removeClass ( 'panning active' ) ;
}
}
function onPanStart ( ) {
if ( ! isSliding ) return ;
element . addClass ( 'panning' ) ;
}
function onPan ( ev ) {
if ( ! isSliding ) return ;
2014-10-12 19:07:47 +02:00
// While panning discrete, update only the
// visual positioning but not the model value.
if ( isDiscrete ) adjustThumbPosition ( ev . center . x ) ;
else doSlide ( ev . center . x ) ;
2014-09-30 12:29:53 +02:00
ev . preventDefault ( ) ;
2014-10-12 19:07:47 +02:00
ev . srcEvent . stopPropagation ( ) ;
}
function onPanEnd ( ev ) {
2014-11-03 17:32:25 +01:00
if ( isDiscrete && ! element [ 0 ] . hasAttribute ( 'disabled' ) ) {
2014-10-12 19:07:47 +02:00
// Convert exact to closest discrete value.
// Slide animate the thumb... and then update the model value.
var exactVal = percentToValue ( positionToPercent ( ev . center . x ) ) ;
var closestVal = minMaxValidator ( stepValidator ( exactVal ) ) ;
setSliderPercent ( valueToPercent ( closestVal ) ) ;
$$rAF ( function ( ) {
setModelValue ( closestVal ) ;
} ) ;
ev . preventDefault ( ) ;
ev . srcEvent . stopPropagation ( ) ;
}
2014-09-30 12:29:53 +02:00
}
/ * *
* Expose for testing
* /
this . _onInput = onInput ;
this . _onPanStart = onPanStart ;
this . _onPan = onPan ;
2014-10-12 19:07:47 +02:00
/ * *
* Slide the UI by changing the model value
* @ param x
* /
function doSlide ( x ) {
scope . $evalAsync ( function ( ) {
setModelValue ( percentToValue ( positionToPercent ( x ) ) ) ;
} ) ;
}
/ * *
* Slide the UI without changing the model ( while dragging / panning )
* @ param x
* /
function adjustThumbPosition ( x ) {
setSliderPercent ( positionToPercent ( x ) ) ;
}
/ * *
* Convert horizontal position on slider to percentage value of offset from beginning ...
* @ param x
* @ returns { number }
* /
function positionToPercent ( x ) {
2014-11-03 17:32:25 +01:00
return Math . max ( 0 , Math . min ( 1 , ( x - sliderDimensions . left ) / ( sliderDimensions . width ) ) ) ;
2014-10-12 19:07:47 +02:00
}
/ * *
* Convert percentage offset on slide to equivalent model value
* @ param percent
* @ returns { * }
* /
function percentToValue ( percent ) {
return ( min + percent * ( max - min ) ) ;
}
function valueToPercent ( val ) {
return ( val - min ) / ( max - min ) ;
2014-09-30 12:29:53 +02:00
}
} ;
}
2014-11-03 17:32:25 +01:00
} ) ( ) ;
2014-09-30 12:29:53 +02:00
2014-11-03 17:32:25 +01:00
( function ( ) {
/ *
2014-09-30 12:29:53 +02:00
* @ ngdoc module
2014-11-03 17:32:25 +01:00
* @ name material . components . sticky
* @ description
*
* Sticky effects for md
2014-09-30 12:29:53 +02:00
* /
2014-11-03 17:32:25 +01:00
angular . module ( 'material.components.sticky' , [
'material.core' ,
'material.components.content' ,
'material.decorators' ,
'material.animations'
2014-10-12 19:07:47 +02:00
] )
2014-11-03 17:32:25 +01:00
. factory ( '$mdSticky' , [
'$document' ,
'$mdEffects' ,
2014-10-12 19:07:47 +02:00
'$compile' ,
2014-11-03 17:32:25 +01:00
'$$rAF' ,
'$mdUtil' ,
MdSticky
2014-10-12 19:07:47 +02:00
] ) ;
2014-11-03 17:32:25 +01:00
/ *
* @ ngdoc service
* @ name $mdSticky
* @ module material . components . sticky
2014-10-12 19:07:47 +02:00
*
* @ description
2014-11-03 17:32:25 +01:00
* The ` $ mdSticky ` service provides a mixin to make elements sticky .
2014-10-12 19:07:47 +02:00
*
2014-11-03 17:32:25 +01:00
* @ returns A ` $ mdSticky ` function that takes three arguments :
* - ` scope `
* - ` element ` : The element that will be 'sticky'
* - ` elementClone ` : A clone of the element , that will be shown
* when the user starts scrolling past the original element .
* If not provided , it will use the result of ` element.clone() ` .
2014-10-12 19:07:47 +02:00
* /
2014-11-03 17:32:25 +01:00
function MdSticky ( $document , $mdEffects , $compile , $$rAF , $mdUtil ) {
2014-10-12 19:07:47 +02:00
2014-11-03 17:32:25 +01:00
var browserStickySupport = checkStickySupport ( ) ;
2014-10-12 19:07:47 +02:00
2014-11-03 17:32:25 +01:00
/ * *
* Registers an element as sticky , used internally by directives to register themselves
* /
return function registerStickyElement ( scope , element , stickyClone ) {
var contentCtrl = element . controller ( 'mdContent' ) ;
if ( ! contentCtrl ) return ;
2014-10-12 19:07:47 +02:00
2014-11-03 17:32:25 +01:00
if ( browserStickySupport ) {
element . css ( {
position : browserStickySupport ,
top : 0 ,
'z-index' : 2
} ) ;
} else {
var $$sticky = contentCtrl . $element . data ( '$$sticky' ) ;
if ( ! $$sticky ) {
$$sticky = setupSticky ( contentCtrl ) ;
contentCtrl . $element . data ( '$$sticky' , $$sticky ) ;
}
2014-09-30 12:29:53 +02:00
2014-11-03 17:32:25 +01:00
var deregister = $$sticky . add ( element , stickyClone || element . clone ( ) ) ;
scope . $on ( '$destroy' , deregister ) ;
}
} ;
2014-09-30 12:29:53 +02:00
2014-11-03 17:32:25 +01:00
function setupSticky ( contentCtrl ) {
var contentEl = contentCtrl . $element ;
2014-09-30 12:29:53 +02:00
2014-11-03 17:32:25 +01:00
// Refresh elements is very expensive, so we use the debounced
// version when possible.
var debouncedRefreshElements = $$rAF . debounce ( refreshElements ) ;
2014-09-30 12:29:53 +02:00
2014-11-03 17:32:25 +01:00
// setupAugmentedScrollEvents gives us `$scrollstart` and `$scroll`,
// more reliable than `scroll` on android.
setupAugmentedScrollEvents ( contentEl ) ;
contentEl . on ( '$scrollstart' , debouncedRefreshElements ) ;
contentEl . on ( '$scroll' , onScroll ) ;
2014-09-30 12:29:53 +02:00
2014-11-03 17:32:25 +01:00
var self ;
return self = {
prev : null ,
current : null , //the currently stickied item
next : null ,
items : [ ] ,
add : add ,
refreshElements : refreshElements
} ;
2014-09-30 12:29:53 +02:00
2014-11-03 17:32:25 +01:00
/ * * * * * * * * * * * * * * *
* Public
* * * * * * * * * * * * * * * /
// Add an element and its sticky clone to this content's sticky collection
function add ( element , stickyClone ) {
stickyClone . addClass ( 'md-sticky-clone' ) ;
2014-09-30 12:29:53 +02:00
2014-11-03 17:32:25 +01:00
var item = {
element : element ,
clone : stickyClone
} ;
self . items . push ( item ) ;
2014-09-30 12:29:53 +02:00
2014-11-03 17:32:25 +01:00
contentEl . parent ( ) . prepend ( item . clone ) ;
2014-09-30 12:29:53 +02:00
2014-11-03 17:32:25 +01:00
debouncedRefreshElements ( ) ;
2014-09-30 12:29:53 +02:00
2014-11-03 17:32:25 +01:00
return function remove ( ) {
self . items . forEach ( function ( item , index ) {
if ( item . element [ 0 ] === element [ 0 ] ) {
self . items . splice ( index , 1 ) ;
item . clone . remove ( ) ;
}
} ) ;
debouncedRefreshElements ( ) ;
} ;
2014-09-30 12:29:53 +02:00
}
2014-11-03 17:32:25 +01:00
function refreshElements ( ) {
var contentRect = contentEl [ 0 ] . getBoundingClientRect ( ) ;
2014-09-30 12:29:53 +02:00
2014-11-03 17:32:25 +01:00
// Sort our collection of elements by their current position in the DOM.
// We need to do this because our elements' order of being added may not
// be the same as their order of display.
self . items . forEach ( refreshPosition ) ;
self . items = self . items . sort ( function ( a , b ) {
return a . top < b . top ? - 1 : 1 ;
} ) ;
2014-09-30 12:29:53 +02:00
2014-11-03 17:32:25 +01:00
// Find which item in the list should be active,
// based upon the content's current scroll position
var item ;
var currentScrollTop = contentEl . prop ( 'scrollTop' ) ;
for ( var i = self . items . length - 1 ; i >= 0 ; i -- ) {
if ( currentScrollTop > self . items [ i ] . top ) {
item = self . items [ i ] ;
break ;
}
2014-10-12 19:07:47 +02:00
}
2014-11-03 17:32:25 +01:00
setCurrentItem ( item ) ;
2014-09-30 12:29:53 +02:00
}
2014-11-03 17:32:25 +01:00
/ * * * * * * * * * * * * * * *
* Private
* * * * * * * * * * * * * * * /
2014-10-12 19:07:47 +02:00
2014-11-03 17:32:25 +01:00
// Find the `top` of an item relative to the content element,
// and also the height.
function refreshPosition ( item ) {
// Find the top of an item by adding to the offsetHeight until we reach the
// content element.
var current = item . element [ 0 ] ;
item . top = 0 ;
item . left = 0 ;
while ( current && current !== contentEl [ 0 ] ) {
item . top += current . offsetTop ;
item . left += current . offsetLeft ;
current = current . offsetParent ;
}
item . height = item . element . prop ( 'offsetHeight' ) ;
2014-11-06 16:55:16 +01:00
item . clone . css ( 'margin-left' , item . left + 'px' ) ;
2014-11-03 17:32:25 +01:00
}
2014-09-30 12:29:53 +02:00
2014-10-12 19:07:47 +02:00
2014-11-03 17:32:25 +01:00
// As we scroll, push in and select the correct sticky element.
function onScroll ( ) {
var scrollTop = contentEl . prop ( 'scrollTop' ) ;
var isScrollingDown = scrollTop > ( onScroll . prevScrollTop || 0 ) ;
onScroll . prevScrollTop = scrollTop ;
2014-09-30 12:29:53 +02:00
2014-11-03 17:32:25 +01:00
// At the top?
if ( scrollTop === 0 ) {
setCurrentItem ( null ) ;
2014-09-30 12:29:53 +02:00
2014-11-03 17:32:25 +01:00
// Going to next item?
} else if ( isScrollingDown && self . next ) {
if ( self . next . top - scrollTop <= 0 ) {
// Sticky the next item if we've scrolled past its position.
setCurrentItem ( self . next ) ;
} else if ( self . current ) {
// Push the current item up when we're almost at the next item.
if ( self . next . top - scrollTop <= self . next . height ) {
translate ( self . current , self . next . top - self . next . height - scrollTop ) ;
} else {
translate ( self . current , null ) ;
}
}
// Scrolling up with a current sticky item?
} else if ( ! isScrollingDown && self . current ) {
if ( scrollTop < self . current . top ) {
// Sticky the previous item if we've scrolled up past
// the original position of the currently stickied item.
setCurrentItem ( self . prev ) ;
}
// Scrolling up, and just bumping into the item above (just set to current)?
// If we have a next item bumping into the current item, translate
// the current item up from the top as it scrolls into view.
if ( self . current && self . next ) {
if ( scrollTop >= self . next . top - self . current . height ) {
translate ( self . current , self . next . top - scrollTop - self . current . height ) ;
} else {
translate ( self . current , null ) ;
}
}
}
}
function setCurrentItem ( item ) {
if ( self . current === item ) return ;
// Deactivate currently active item
if ( self . current ) {
translate ( self . current , null ) ;
setStickyState ( self . current , null ) ;
}
2014-09-30 12:29:53 +02:00
2014-11-03 17:32:25 +01:00
// Activate new item if given
if ( item ) {
setStickyState ( item , 'active' ) ;
}
2014-09-30 12:29:53 +02:00
2014-11-03 17:32:25 +01:00
self . current = item ;
var index = self . items . indexOf ( item ) ;
// If index === -1, index + 1 = 0. It works out.
self . next = self . items [ index + 1 ] ;
self . prev = self . items [ index - 1 ] ;
setStickyState ( self . next , 'next' ) ;
setStickyState ( self . prev , 'prev' ) ;
}
2014-09-30 12:29:53 +02:00
2014-11-03 17:32:25 +01:00
function setStickyState ( item , state ) {
if ( ! item || item . state === state ) return ;
if ( item . state ) {
item . clone . attr ( 'sticky-prev-state' , item . state ) ;
item . element . attr ( 'sticky-prev-state' , item . state ) ;
}
item . clone . attr ( 'sticky-state' , state ) ;
item . element . attr ( 'sticky-state' , state ) ;
item . state = state ;
}
2014-09-30 12:29:53 +02:00
2014-11-03 17:32:25 +01:00
function translate ( item , amount ) {
if ( ! item ) return ;
if ( amount === null || amount === undefined ) {
if ( item . translateY ) {
item . translateY = null ;
item . clone . css ( $mdEffects . TRANSFORM , '' ) ;
}
} else {
item . translateY = amount ;
item . clone . css (
$mdEffects . TRANSFORM ,
'translate3d(' + item . left + 'px,' + amount + 'px,0)'
) ;
}
}
}
2014-09-30 12:29:53 +02:00
2014-11-03 17:32:25 +01:00
// Function to check for browser sticky support
function checkStickySupport ( $el ) {
var stickyProp ;
var testEl = angular . element ( '<div>' ) ;
$document [ 0 ] . body . appendChild ( testEl [ 0 ] ) ;
2014-09-30 12:29:53 +02:00
2014-11-03 17:32:25 +01:00
var stickyProps = [ 'sticky' , '-webkit-sticky' ] ;
for ( var i = 0 ; i < stickyProps . length ; ++ i ) {
testEl . css ( { position : stickyProps [ i ] , top : 0 , 'z-index' : 2 } ) ;
if ( testEl . css ( 'position' ) == stickyProps [ i ] ) {
stickyProp = stickyProps [ i ] ;
break ;
2014-10-12 19:07:47 +02:00
}
}
2014-11-03 17:32:25 +01:00
testEl . remove ( ) ;
return stickyProp ;
}
2014-09-30 12:29:53 +02:00
2014-11-03 17:32:25 +01:00
// Android 4.4 don't accurately give scroll events.
// To fix this problem, we setup a fake scroll event. We say:
// > If a scroll or touchmove event has happened in the last DELAY milliseconds,
// then send a `$scroll` event every animationFrame.
// Additionally, we add $scrollstart and $scrollend events.
function setupAugmentedScrollEvents ( element ) {
var SCROLL _END _DELAY = 200 ;
var isScrolling ;
var lastScrollTime ;
element . on ( 'scroll touchmove' , function ( ) {
if ( ! isScrolling ) {
isScrolling = true ;
$$rAF ( loopScrollEvent ) ;
element . triggerHandler ( '$scrollstart' ) ;
2014-10-12 19:07:47 +02:00
}
2014-11-03 17:32:25 +01:00
element . triggerHandler ( '$scroll' ) ;
lastScrollTime = + $mdUtil . now ( ) ;
} ) ;
2014-09-30 12:29:53 +02:00
2014-11-03 17:32:25 +01:00
function loopScrollEvent ( ) {
if ( + $mdUtil . now ( ) - lastScrollTime > SCROLL _END _DELAY ) {
isScrolling = false ;
element . triggerHandler ( '$scrollend' ) ;
} else {
element . triggerHandler ( '$scroll' ) ;
$$rAF ( loopScrollEvent ) ;
2014-09-30 12:29:53 +02:00
}
2014-10-12 19:07:47 +02:00
}
2014-11-03 17:32:25 +01:00
}
2014-09-30 12:29:53 +02:00
2014-11-03 17:32:25 +01:00
}
} ) ( ) ;
2014-10-12 19:07:47 +02:00
2014-11-03 17:32:25 +01:00
( function ( ) {
/ * *
* @ ngdoc module
* @ name material . components . subheader
* @ description
* SubHeader module
* /
angular . module ( 'material.components.subheader' , [
2014-11-06 16:55:16 +01:00
'material.components.sticky' ,
'material.services.theming'
2014-11-03 17:32:25 +01:00
] )
. directive ( 'mdSubheader' , [
'$mdSticky' ,
'$compile' ,
2014-11-06 16:55:16 +01:00
'$mdTheming' ,
2014-11-03 17:32:25 +01:00
MdSubheaderDirective
] ) ;
2014-10-12 19:07:47 +02:00
2014-11-03 17:32:25 +01:00
/ * *
* @ ngdoc directive
* @ name mdSubheader
* @ module material . components . subheader
*
* @ restrict E
*
* @ description
* The ` <md-subheader> ` directive is a subheader for a section
*
* @ usage
* < hljs lang = "html" >
* < md - subheader > Online Friends < / m d - s u b h e a d e r >
* < / h l j s >
* /
2014-10-12 19:07:47 +02:00
2014-11-06 16:55:16 +01:00
function MdSubheaderDirective ( $mdSticky , $compile , $mdTheming ) {
2014-11-03 17:32:25 +01:00
return {
restrict : 'E' ,
replace : true ,
transclude : true ,
template :
'<h2 class="md-subheader">' +
'<span class="md-subheader-content"></span>' +
'</h2>' ,
compile : function ( element , attr , transclude ) {
var outerHTML = element [ 0 ] . outerHTML ;
return function postLink ( scope , element , attr ) {
2014-11-06 16:55:16 +01:00
$mdTheming ( element ) ;
2014-11-03 17:32:25 +01:00
function getContent ( el ) {
return angular . element ( el [ 0 ] . querySelector ( '.md-subheader-content' ) ) ;
}
2014-10-12 19:07:47 +02:00
2014-11-03 17:32:25 +01:00
// Transclude the user-given contents of the subheader
// the conventional way.
transclude ( scope , function ( clone ) {
getContent ( element ) . append ( clone ) ;
} ) ;
2014-09-30 12:29:53 +02:00
2014-11-03 17:32:25 +01:00
// Create another clone, that uses the outer and inner contents
// of the element, that will be 'stickied' as the user scrolls.
transclude ( scope , function ( clone ) {
var stickyClone = $compile ( angular . element ( outerHTML ) ) ( scope ) ;
2014-11-06 16:55:16 +01:00
$mdTheming ( stickyClone ) ;
2014-11-03 17:32:25 +01:00
getContent ( stickyClone ) . append ( clone ) ;
$mdSticky ( scope , element , stickyClone ) ;
} ) ;
} ;
}
} ;
}
} ) ( ) ;
2014-09-30 12:29:53 +02:00
2014-11-03 17:32:25 +01:00
( function ( ) {
( function ( ) {
2014-10-12 19:07:47 +02:00
2014-11-03 17:32:25 +01:00
/ * *
* @ ngdoc module
* @ name material . components . swipe
* @ description Swipe module !
* /
angular . module ( 'material.components.swipe' , [ 'ng' ] )
2014-09-30 12:29:53 +02:00
2014-11-03 17:32:25 +01:00
/ *
* @ ngdoc service
* @ module material . components . swipe
* @ name $mdSwipe
* @ description
* This service allows directives to easily attach swipe and pan listeners to
* the specified element .
* /
. factory ( "$mdSwipe" , function ( ) {
2014-09-30 12:29:53 +02:00
2014-11-03 17:32:25 +01:00
// match expected API functionality
var attachNoop = function ( ) { return angular . noop ; } ;
2014-09-30 12:29:53 +02:00
2014-11-03 17:32:25 +01:00
/ * *
* SwipeService constructor pre - captures scope and customized event types
*
* @ param scope
* @ param eventTypes
* @ returns { * }
* @ constructor
* /
return function SwipeService ( scope , eventTypes ) {
if ( ! eventTypes ) eventTypes = "swipeleft swiperight" ;
2014-10-12 19:07:47 +02:00
2014-11-03 17:32:25 +01:00
// publish configureFor() method for specific element instance
return function configureFor ( element , onSwipeCallback , attachLater ) {
var hammertime = new Hammer ( element [ 0 ] , {
recognizers : addRecognizers ( [ ] , eventTypes )
} ) ;
2014-10-12 19:07:47 +02:00
2014-11-03 17:32:25 +01:00
// Attach swipe listeners now
if ( ! attachLater ) attachSwipe ( ) ;
2014-10-12 19:07:47 +02:00
2014-11-03 17:32:25 +01:00
// auto-disconnect during destroy
scope . $on ( '$destroy' , function ( ) {
hammertime . destroy ( ) ;
} ) ;
2014-09-30 12:29:53 +02:00
2014-11-03 17:32:25 +01:00
return attachSwipe ;
2014-09-30 12:29:53 +02:00
2014-11-03 17:32:25 +01:00
// **********************
// Internal methods
// **********************
2014-09-30 12:29:53 +02:00
2014-11-03 17:32:25 +01:00
/ * *
* Delegate swipe event to callback function
* and ensure $digest is triggered .
*
* @ param ev HammerEvent
* /
function swipeHandler ( ev ) {
2014-09-30 12:29:53 +02:00
2014-11-03 17:32:25 +01:00
// Prevent triggering parent hammer listeners
ev . srcEvent . stopPropagation ( ) ;
2014-09-30 12:29:53 +02:00
2014-11-03 17:32:25 +01:00
if ( angular . isFunction ( onSwipeCallback ) ) {
scope . $apply ( function ( ) {
onSwipeCallback ( ev ) ;
} ) ;
}
}
2014-09-30 12:29:53 +02:00
2014-11-03 17:32:25 +01:00
/ * *
* Enable listeners and return detach ( ) fn
* /
function attachSwipe ( ) {
hammertime . on ( eventTypes , swipeHandler ) ;
2014-10-12 19:07:47 +02:00
2014-11-03 17:32:25 +01:00
return function detachSwipe ( ) {
hammertime . off ( eventTypes ) ;
} ;
}
2014-10-12 19:07:47 +02:00
2014-11-03 17:32:25 +01:00
/ * *
* Add optional recognizers such as panleft , panright
* /
function addRecognizers ( list , events ) {
var hasPanning = ( events . indexOf ( "pan" ) > - 1 ) ;
var hasSwipe = ( events . indexOf ( "swipe" ) > - 1 ) ;
2014-10-12 19:07:47 +02:00
2014-11-03 17:32:25 +01:00
if ( hasPanning ) {
list . push ( [ Hammer . Pan , { direction : Hammer . DIRECTION _HORIZONTAL } ] ) ;
}
if ( hasSwipe ) {
list . push ( [ Hammer . Swipe , { direction : Hammer . DIRECTION _HORIZONTAL } ] ) ;
}
2014-09-30 12:29:53 +02:00
2014-11-03 17:32:25 +01:00
return list ;
}
2014-10-12 19:07:47 +02:00
2014-11-03 17:32:25 +01:00
} ;
} ;
} )
2014-10-12 19:07:47 +02:00
2014-11-03 17:32:25 +01:00
/ * *
* @ ngdoc directive
* @ module material . components . swipe
* @ name mdSwipeLeft
*
* @ restrict A
*
* @ description
2014-11-06 16:55:16 +01:00
* The ` <div md-swipe-left="expression"> ` directive identifies an element on which
2014-11-03 17:32:25 +01:00
* HammerJS horizontal swipe left and pan left support will be active . The swipe / pan action
2014-11-06 16:55:16 +01:00
* can result in custom activity trigger by evaluating ` expression ` .
2014-11-03 17:32:25 +01:00
*
* @ param { boolean = } noPan Use of attribute indicates flag to disable detection of ` panleft ` activity
*
* @ usage
* < hljs lang = "html" >
*
* < div class = "animate-switch-container"
* ng - switch on = "data.selectedIndex"
* md - swipe - left = "data.selectedIndex+=1;"
* md - swipe - right = "data.selectedIndex-=1;" >
*
* < / d i v >
* < / h l j s >
*
* /
. directive ( "mdSwipeLeft" , [ '$parse' , '$mdSwipe' ,
function MdSwipeLeft ( $parse , $mdSwipe ) {
return {
restrict : 'A' ,
link : swipePostLink ( $parse , $mdSwipe , "SwipeLeft" )
} ;
} ] )
2014-10-12 19:07:47 +02:00
2014-11-03 17:32:25 +01:00
/ * *
* @ ngdoc directive
* @ module material . components . swipe
* @ name mdSwipeRight
*
* @ restrict A
*
* @ description
2014-11-06 16:55:16 +01:00
* The ` <div md-swipe-right="expression"> ` directive identifies functionality
2014-11-03 17:32:25 +01:00
* that attaches HammerJS horizontal swipe right and pan right support to an element . The swipe / pan action
2014-11-06 16:55:16 +01:00
* can result in activity trigger by evaluating ` expression `
2014-11-03 17:32:25 +01:00
*
* @ param { boolean = } noPan Use of attribute indicates flag to disable detection of ` panright ` activity
*
* @ usage
* < hljs lang = "html" >
*
* < div class = "animate-switch-container"
* ng - switch on = "data.selectedIndex"
* md - swipe - left = "data.selectedIndex+=1;"
* md - swipe - right = "data.selectedIndex-=1;" >
*
* < / d i v >
* < / h l j s >
*
* /
. directive ( "mdSwipeRight" , [ '$parse' , '$mdSwipe' ,
function MdSwipeRight ( $parse , $mdSwipe ) {
return {
restrict : 'A' ,
link : swipePostLink ( $parse , $mdSwipe , "SwipeRight" )
} ;
}
] ) ;
2014-10-12 19:07:47 +02:00
2014-11-03 17:32:25 +01:00
/ * *
* Factory to build PostLink function specific to Swipe or Pan direction
*
* @ param $parse
* @ param $mdSwipe
* @ param name
* @ returns { Function }
* /
function swipePostLink ( $parse , $mdSwipe , name ) {
2014-10-12 19:07:47 +02:00
2014-11-03 17:32:25 +01:00
return function ( scope , element , attrs ) {
var direction = name . toLowerCase ( ) ;
var directiveName = "md" + name ;
2014-10-12 19:07:47 +02:00
2014-11-03 17:32:25 +01:00
var parentGetter = $parse ( attrs [ directiveName ] ) || angular . noop ;
var configureSwipe = $mdSwipe ( scope , direction ) ;
var requestSwipe = function ( locals ) {
// build function to request scope-specific swipe response
parentGetter ( scope , locals ) ;
} ;
2014-10-12 19:07:47 +02:00
2014-11-03 17:32:25 +01:00
configureSwipe ( element , function onHandleSwipe ( ev ) {
if ( ev . type == direction ) {
requestSwipe ( ) ;
}
} ) ;
2014-10-12 19:07:47 +02:00
2014-11-03 17:32:25 +01:00
}
}
2014-10-12 19:07:47 +02:00
2014-11-03 17:32:25 +01:00
} ) ( ) ;
2014-10-12 19:07:47 +02:00
2014-11-03 17:32:25 +01:00
} ) ( ) ;
2014-09-30 12:29:53 +02:00
2014-11-03 17:32:25 +01:00
( function ( ) {
/ * *
* @ private
* @ ngdoc module
* @ name material . components . switch
* /
2014-09-30 12:29:53 +02:00
2014-11-03 17:32:25 +01:00
angular . module ( 'material.components.switch' , [
'material.components.checkbox' ,
2014-11-06 16:55:16 +01:00
'material.components.radioButton' ,
'material.services.theming'
2014-11-03 17:32:25 +01:00
] )
2014-10-12 19:07:47 +02:00
2014-11-03 17:32:25 +01:00
. directive ( 'mdSwitch' , [
'mdCheckboxDirective' ,
'mdRadioButtonDirective' ,
2014-11-06 16:55:16 +01:00
'$mdTheming' ,
2014-11-03 17:32:25 +01:00
MdSwitch
] ) ;
2014-09-30 12:29:53 +02:00
2014-11-03 17:32:25 +01:00
/ * *
* @ private
* @ ngdoc directive
* @ module material . components . switch
* @ name mdSwitch
* @ restrict E
*
* The switch directive is used very much like the normal [ angular checkbox ] ( https : //docs.angularjs.org/api/ng/input/input%5Bcheckbox%5D).
*
* @ param { string } ngModel Assignable angular expression to data - bind to .
* @ param { string = } name Property name of the form under which the control is published .
* @ param { expression = } ngTrueValue The value to which the expression should be set when selected .
* @ param { expression = } ngFalseValue The value to which the expression should be set when not selected .
* @ param { string = } ngChange Angular expression to be executed when input changes due to user interaction with the input element .
* @ param { boolean = } noink Use of attribute indicates use of ripple ink effects .
* @ param { boolean = } disabled Use of attribute indicates the switch is disabled : no ink effects and not selectable
* @ param { string = } ariaLabel Publish the button label used by screen - readers for accessibility . Defaults to the switch ' s text .
*
* @ usage
* < hljs lang = "html" >
* < md - switch ng - model = "isActive" aria - label = "Finished?" >
* Finished ?
* < / m d - s w i t c h >
*
* < md - switch noink ng - model = "hasInk" aria - label = "No Ink Effects" >
* No Ink Effects
* < / m d - s w i t c h >
*
* < md - switch disabled ng - model = "isDisabled" aria - label = "Disabled" >
* Disabled
* < / m d - s w i t c h >
*
* < / h l j s >
* /
2014-11-06 16:55:16 +01:00
function MdSwitch ( checkboxDirectives , radioButtonDirectives , $mdTheming ) {
2014-11-03 17:32:25 +01:00
var checkboxDirective = checkboxDirectives [ 0 ] ;
var radioButtonDirective = radioButtonDirectives [ 0 ] ;
2014-10-12 19:07:47 +02:00
2014-11-03 17:32:25 +01:00
return {
restrict : 'E' ,
transclude : true ,
template :
'<div class="md-switch-bar"></div>' +
'<div class="md-switch-thumb">' +
radioButtonDirective . template +
'</div>' ,
require : '?ngModel' ,
compile : compile
} ;
2014-10-12 19:07:47 +02:00
2014-11-03 17:32:25 +01:00
function compile ( element , attr ) {
var thumb = angular . element ( element [ 0 ] . querySelector ( '.md-switch-thumb' ) ) ;
//Copy down disabled attributes for checkboxDirective to use
thumb . attr ( 'disabled' , attr . disabled ) ;
thumb . attr ( 'ngDisabled' , attr . ngDisabled ) ;
2014-10-12 19:07:47 +02:00
2014-11-03 17:32:25 +01:00
var link = checkboxDirective . compile ( thumb , attr ) ;
2014-09-30 12:29:53 +02:00
2014-11-03 17:32:25 +01:00
return function ( scope , element , attr , ngModelCtrl ) {
2014-11-06 16:55:16 +01:00
$mdTheming ( element ) ;
2014-11-03 17:32:25 +01:00
var thumb = angular . element ( element [ 0 ] . querySelector ( '.md-switch-thumb' ) ) ;
2014-11-06 16:55:16 +01:00
return link ( scope , thumb , attr , ngModelCtrl ) ;
2014-11-03 17:32:25 +01:00
} ;
2014-09-30 12:29:53 +02:00
}
2014-11-03 17:32:25 +01:00
}
} ) ( ) ;
2014-09-30 12:29:53 +02:00
2014-11-03 17:32:25 +01:00
( function ( ) {
/ * *
* @ ngdoc module
* @ name material . components . tabs
* @ description
*
* Tabs
* /
angular . module ( 'material.components.tabs' , [
'material.core' ,
'material.animations' ,
2014-11-06 16:55:16 +01:00
'material.components.swipe' ,
'material.services.theming'
2014-11-03 17:32:25 +01:00
] ) ;
} ) ( ) ;
2014-09-30 12:29:53 +02:00
2014-11-03 17:32:25 +01:00
( function ( ) {
/ * *
* @ ngdoc module
* @ name material . components . textField
* @ description
* Form
* /
2014-11-06 16:55:16 +01:00
angular . module ( 'material.components.textField' , [ 'material.core' , 'material.services.theming' ] )
2014-11-03 17:32:25 +01:00
. directive ( 'mdInputGroup' , [ mdInputGroupDirective ] )
. directive ( 'mdInput' , [ '$mdUtil' , mdInputDirective ] )
2014-11-06 16:55:16 +01:00
. directive ( 'mdTextFloat' , [ '$mdTheming' , '$mdUtil' , mdTextFloatDirective ] ) ;
2014-10-12 19:07:47 +02:00
2014-09-30 12:29:53 +02:00
/ * *
* @ ngdoc directive
2014-11-03 17:32:25 +01:00
* @ name mdTextFloat
* @ module material . components . textField
2014-09-30 12:29:53 +02:00
*
* @ restrict E
*
* @ description
2014-11-03 17:32:25 +01:00
* Use the ` <md-text-float> ` directive to quickly construct ` Floating Label ` text fields
2014-09-30 12:29:53 +02:00
*
2014-11-06 16:55:16 +01:00
* @ param { string } fid Attribute used for accessibility link pairing between the Label and Input elements
* @ param { string = } type Optional value to define the type of input field . Defaults to string .
2014-11-03 17:32:25 +01:00
* @ param { string } label Attribute to specify the input text field hint .
* @ param { string = } ng - model Optional value to assign as existing input text string
2014-09-30 12:29:53 +02:00
*
2014-11-03 17:32:25 +01:00
* @ usage
* < hljs lang = "html" >
* < md - text - float label = "LastName" ng - model = "user.lastName" > < / m d - t e x t - f l o a t >
2014-09-30 12:29:53 +02:00
*
2014-11-03 17:32:25 +01:00
* <!-- Specify a read - only input field by using the ` disabled ` attribute -- >
* < md - text - float label = "Company" ng - model = "user.company" disabled > < / m d - t e x t - f l o a t >
2014-09-30 12:29:53 +02:00
*
2014-11-03 17:32:25 +01:00
* <!-- Specify an input type if desired . -- >
* < md - text - float label = "eMail" ng - model = "user.email" type = "email" > < / m d - t e x t - f l o a t >
* < / h l j s >
* /
2014-11-06 16:55:16 +01:00
function mdTextFloatDirective ( $mdTheming , $mdUtil ) {
2014-11-03 17:32:25 +01:00
return {
restrict : 'E' ,
replace : true ,
scope : {
fid : '@?' ,
2014-11-06 16:55:16 +01:00
label : '@?' ,
2014-11-03 17:32:25 +01:00
value : '=ngModel'
} ,
2014-11-06 16:55:16 +01:00
compile : function ( element , attr ) {
if ( angular . isUndefined ( attr . fid ) ) {
attr . fid = $mdUtil . nextUid ( ) ;
}
2014-11-03 17:32:25 +01:00
return {
pre : function ( scope , element , attrs ) {
// transpose `disabled` flag
if ( angular . isDefined ( attrs . disabled ) ) {
element . attr ( 'disabled' , true ) ;
scope . isDisabled = true ;
}
2014-11-06 16:55:16 +01:00
scope . inputType = attrs . type || "text" ;
element . removeAttr ( 'type' ) ;
2014-11-03 17:32:25 +01:00
2014-11-06 16:55:16 +01:00
// transpose optional `class` settings
2014-11-03 17:32:25 +01:00
element . attr ( 'class' , attrs . class ) ;
2014-11-06 16:55:16 +01:00
} ,
post : $mdTheming
2014-11-03 17:32:25 +01:00
} ;
} ,
template :
2014-11-06 16:55:16 +01:00
'<md-input-group ng-disabled="isDisabled" tabindex="-1">' +
' <label for="{{fid}}" >{{label}}</label>' +
' <md-input id="{{fid}}" ng-model="value" type="{{inputType}}"></md-input>' +
2014-11-03 17:32:25 +01:00
'</md-input-group>'
} ;
}
/ *
* @ private
2014-09-30 12:29:53 +02:00
*
2014-11-03 17:32:25 +01:00
* @ ngdoc directive
* @ name mdInputGroup
* @ module material . components . textField
* @ restrict E
* @ description
* Use the ` <md-input-group> ` directive as the grouping parent of a ` <md-input> ` element .
2014-09-30 12:29:53 +02:00
*
2014-11-03 17:32:25 +01:00
* @ usage
2014-09-30 12:29:53 +02:00
* < hljs lang = "html" >
2014-11-03 17:32:25 +01:00
* < md - input - group ng - disabled = "isDisabled" >
* < label for = "{{fid}}" > { { someLabel } } < / l a b e l >
* < md - input id = "{{fid}}" type = "text" ng - model = "someText" > < / m d - i n p u t >
* < / m d - i n p u t - g r o u p >
2014-09-30 12:29:53 +02:00
* < / h l j s >
2014-11-03 17:32:25 +01:00
* /
function mdInputGroupDirective ( ) {
return {
restrict : 'CE' ,
controller : [ '$element' , function ( $element ) {
this . setFocused = function ( isFocused ) {
$element . toggleClass ( 'md-input-focused' , ! ! isFocused ) ;
} ;
this . setHasValue = function ( hasValue ) {
2014-11-06 16:55:16 +01:00
$element . toggleClass ( 'md-input-has-value' , hasValue ) ;
2014-11-03 17:32:25 +01:00
} ;
} ]
} ;
2014-11-06 16:55:16 +01:00
2014-11-03 17:32:25 +01:00
}
/ *
* @ private
*
* @ ngdoc directive
* @ name mdInput
* @ module material . components . textField
2014-09-30 12:29:53 +02:00
*
2014-11-03 17:32:25 +01:00
* @ restrict E
*
* @ description
* Use the ` <md-input> ` directive as elements within a ` <md-input-group> ` container
*
* @ usage
* < hljs lang = "html" >
* < md - input - group ng - disabled = "user.isLocked" >
* < label for = "i1" > FirstName < / l a b e l >
* < md - input id = "i1" ng - model = "user.firstName" > < / m d - i n p u t >
* < / m d - i n p u t - g r o u p >
* < / h l j s >
2014-09-30 12:29:53 +02:00
* /
2014-11-03 17:32:25 +01:00
function mdInputDirective ( $mdUtil ) {
2014-09-30 12:29:53 +02:00
return {
restrict : 'E' ,
2014-11-03 17:32:25 +01:00
replace : true ,
template : '<input >' ,
require : [ '^?mdInputGroup' , '?ngModel' ] ,
link : function ( scope , element , attr , ctrls ) {
var inputGroupCtrl = ctrls [ 0 ] ;
var ngModelCtrl = ctrls [ 1 ] ;
if ( ! inputGroupCtrl ) {
return ;
2014-10-12 19:07:47 +02:00
}
2014-11-03 17:32:25 +01:00
// scan for disabled and transpose the `type` value to the <input> element
var isDisabled = $mdUtil . isParentDisabled ( element ) ;
2014-10-12 19:07:47 +02:00
2014-11-03 17:32:25 +01:00
element . attr ( 'tabindex' , isDisabled ? - 1 : 0 ) ;
2014-11-06 16:55:16 +01:00
element . attr ( 'aria-disabled' , isDisabled ? 'true' : 'false' ) ;
2014-11-03 17:32:25 +01:00
element . attr ( 'type' , attr . type || element . parent ( ) . attr ( 'type' ) || "text" ) ;
2014-09-30 12:29:53 +02:00
2014-11-03 17:32:25 +01:00
// When the input value changes, check if it "has" a value, and
// set the appropriate class on the input group
if ( ngModelCtrl ) {
//Add a $formatter so we don't use up the render function
ngModelCtrl . $formatters . push ( function ( value ) {
2014-11-06 16:55:16 +01:00
inputGroupCtrl . setHasValue ( isNotEmpty ( value ) ) ;
2014-11-03 17:32:25 +01:00
return value ;
2014-09-30 12:29:53 +02:00
} ) ;
2014-10-12 19:07:47 +02:00
}
2014-09-30 12:29:53 +02:00
2014-11-03 17:32:25 +01:00
element . on ( 'input' , function ( ) {
2014-11-06 16:55:16 +01:00
inputGroupCtrl . setHasValue ( isNotEmpty ( ) ) ;
2014-11-03 17:32:25 +01:00
} ) ;
2014-09-30 12:29:53 +02:00
2014-11-03 17:32:25 +01:00
// When the input focuses, add the focused class to the group
element . on ( 'focus' , function ( e ) {
inputGroupCtrl . setFocused ( true ) ;
} ) ;
// When the input blurs, remove the focused class from the group
element . on ( 'blur' , function ( e ) {
inputGroupCtrl . setFocused ( false ) ;
2014-11-06 16:55:16 +01:00
inputGroupCtrl . setHasValue ( isNotEmpty ( ) ) ;
2014-11-03 17:32:25 +01:00
} ) ;
2014-09-30 12:29:53 +02:00
2014-11-03 17:32:25 +01:00
scope . $on ( '$destroy' , function ( ) {
inputGroupCtrl . setFocused ( false ) ;
inputGroupCtrl . setHasValue ( false ) ;
} ) ;
2014-11-06 16:55:16 +01:00
function isNotEmpty ( value ) {
value = angular . isUndefined ( value ) ? element . val ( ) : value ;
return ( angular . isDefined ( value ) && ( value !== null ) &&
( value . toString ( ) . trim ( ) != "" ) ) ;
}
2014-11-03 17:32:25 +01:00
}
} ;
}
2014-09-30 12:29:53 +02:00
2014-10-12 19:07:47 +02:00
2014-11-03 17:32:25 +01:00
} ) ( ) ;
2014-09-30 12:29:53 +02:00
2014-11-03 17:32:25 +01:00
( function ( ) {
/ * *
* @ ngdoc module
* @ name material . components . toast
* @ description
* Toast
* /
angular . module ( 'material.components.toast' , [
'material.services.interimElement' ,
'material.components.swipe'
] )
. directive ( 'mdToast' , [
MdToastDirective
] )
. factory ( '$mdToast' , [
'$timeout' ,
'$$interimElement' ,
'$animate' ,
'$mdSwipe' ,
MdToastService
] ) ;
2014-09-30 12:29:53 +02:00
2014-11-03 17:32:25 +01:00
function MdToastDirective ( ) {
return {
restrict : 'E'
} ;
2014-10-12 19:07:47 +02:00
}
2014-09-30 12:29:53 +02:00
2014-11-03 17:32:25 +01:00
/ * *
* @ ngdoc service
* @ name $mdToast
* @ module material . components . toast
*
* @ description
* ` $ mdToast ` opens a toast nofication on any position on the screen with an optional
* duration , and provides a simple promise API .
*
2014-11-06 16:55:16 +01:00
*
2014-11-03 17:32:25 +01:00
* # # # Restrictions
* - The toast ' s template must have an outer ` <md-toast> ` element .
2014-11-06 16:55:16 +01:00
* - For a toast action , use element with class ` md-action ` .
* - Add the class ` md-capsule ` for curved corners .
2014-11-03 17:32:25 +01:00
*
* @ usage
* < hljs lang = "html" >
* < div ng - controller = "MyController" >
* < md - button ng - click = "openToast()" >
* Open a Toast !
* < / m d - b u t t o n >
* < / d i v >
* < / h l j s >
*
* < hljs lang = "js" >
* var app = angular . module ( 'app' , [ 'ngMaterial' ] ) ;
* app . controller ( 'MyController' , function ( $scope , $mdToast ) {
* $scope . openToast = function ( $event ) {
* $mdToast . show ( {
* template : '<md-toast>Hello!</md-toast>' ,
* hideDelay : 3000
* } ) ;
* } ;
* } ) ;
* < / h l j s >
* /
2014-09-30 12:29:53 +02:00
2014-11-03 17:32:25 +01:00
/ * *
* @ ngdoc method
* @ name $mdToast # show
*
* @ description
* Show a toast dialog with the specified options .
*
* @ param { object } options An options object , with the following properties :
*
* - ` templateUrl ` - ` {string=} ` : The url of an html template file that will
* be used as the content of the toast . Restrictions : the template must
* have an outer ` md-toast ` element .
* - ` template ` - ` {string=} ` : Same as templateUrl , except this is an actual
* template string .
* - ` hideDelay ` - ` {number=} ` : How many milliseconds the toast should stay
* active before automatically closing . Set to 0 or false to have the toast stay open until
* closed manually . Default : 3000.
* - ` position ` - ` {string=} ` : Where to place the toast . Available : any combination
* of 'bottom' , 'left' , 'top' , 'right' , 'fit' . Default : 'bottom left' .
* - ` controller ` - ` {string=} ` : The controller to associate with this toast .
* The controller will be injected the local ` $ hideToast ` , which is a function
* used to hide the toast .
* - ` locals ` - ` {string=} ` : An object containing key / value pairs . The keys will
* be used as names of values to inject into the controller . For example ,
* ` locals: {three: 3} ` would inject ` three ` into the controller with the value
* of 3.
* - ` resolve ` - ` {object=} ` : Similar to locals , except it takes promises as values
* and the toast will not open until the promises resolve .
* - ` controllerAs ` - ` {string=} ` : An alias to assign the controller to on the scope .
*
* @ returns { promise } A promise that can be resolved with ` $ mdToast.hide() ` or
* rejected with ` $ mdBottomSheet.cancel() ` .
* /
2014-09-30 12:29:53 +02:00
2014-11-03 17:32:25 +01:00
/ * *
* @ ngdoc method
* @ name $mdToast # hide
*
* @ description
* Hide the existing toast and resolve the promise returned from ` $ mdToast.show() ` .
*
* @ param { *= } response An argument for the resolved promise .
*
* /
2014-09-30 12:29:53 +02:00
2014-11-03 17:32:25 +01:00
/ * *
* @ ngdoc method
* @ name $mdToast # cancel
*
* @ description
* Hide the existing toast and reject the promise returned from
* ` $ mdToast.show() ` .
*
* @ param { *= } response An argument for the rejected promise .
*
* /
2014-09-30 12:29:53 +02:00
2014-11-06 16:55:16 +01:00
function MdToastService ( $timeout , $$interimElement , $animate , $mdSwipe , $mdTheming ) {
2014-09-30 12:29:53 +02:00
2014-11-03 17:32:25 +01:00
var factoryDef = {
onShow : onShow ,
onRemove : onRemove ,
position : 'bottom left' ,
2014-11-06 16:55:16 +01:00
themable : true ,
2014-11-03 17:32:25 +01:00
hideDelay : 3000 ,
} ;
2014-09-30 12:29:53 +02:00
2014-11-03 17:32:25 +01:00
var $mdToast = $$interimElement ( factoryDef ) ;
return $mdToast ;
2014-09-30 12:29:53 +02:00
2014-11-03 17:32:25 +01:00
function onShow ( scope , element , options ) {
2014-11-06 16:55:16 +01:00
// 'top left' -> 'md-top md-left'
element . addClass ( options . position . split ( ' ' ) . map ( function ( pos ) {
return 'md-' + pos ;
} ) . join ( ' ' ) ) ;
2014-11-03 17:32:25 +01:00
options . parent . addClass ( toastOpenClass ( options . position ) ) ;
2014-09-30 12:29:53 +02:00
2014-11-03 17:32:25 +01:00
var configureSwipe = $mdSwipe ( scope , 'swipeleft swiperight' ) ;
options . detachSwipe = configureSwipe ( element , function ( ev ) {
//Add swipeleft/swiperight class to element so it can animate correctly
2014-11-06 16:55:16 +01:00
element . addClass ( 'md-' + ev . type ) ;
2014-11-03 17:32:25 +01:00
$timeout ( $mdToast . hide ) ;
} ) ;
2014-09-30 12:29:53 +02:00
2014-11-03 17:32:25 +01:00
return $animate . enter ( element , options . parent ) ;
}
2014-09-30 12:29:53 +02:00
2014-11-03 17:32:25 +01:00
function onRemove ( scope , element , options ) {
options . detachSwipe ( ) ;
options . parent . removeClass ( toastOpenClass ( options . position ) ) ;
return $animate . leave ( element ) ;
}
2014-09-30 12:29:53 +02:00
2014-11-03 17:32:25 +01:00
function toastOpenClass ( position ) {
return 'md-toast-open-' +
( position . indexOf ( 'top' ) > - 1 ? 'top' : 'bottom' ) ;
2014-10-12 19:07:47 +02:00
}
2014-11-03 17:32:25 +01:00
}
} ) ( ) ;
2014-09-30 12:29:53 +02:00
2014-11-03 17:32:25 +01:00
( function ( ) {
/ * *
* @ ngdoc module
* @ name material . components . toolbar
* /
angular . module ( 'material.components.toolbar' , [
'material.core' ,
'material.components.content' ,
2014-11-06 16:55:16 +01:00
'material.services.theming' ,
2014-11-03 17:32:25 +01:00
'material.animations'
] )
. directive ( 'mdToolbar' , [
'$$rAF' ,
'$mdEffects' ,
'$mdUtil' ,
2014-11-06 16:55:16 +01:00
'$mdTheming' ,
2014-11-03 17:32:25 +01:00
mdToolbarDirective
] ) ;
2014-09-30 12:29:53 +02:00
2014-11-03 17:32:25 +01:00
/ * *
* @ ngdoc directive
* @ name mdToolbar
* @ module material . components . toolbar
* @ restrict E
* @ description
* ` md-toolbar ` is used to place a toolbar in your app .
*
* Toolbars are usually used above a content area to display the title of the
* current page , and show relevant action buttons for that page .
*
* You can change the height of the toolbar by adding either the
* ` md-medium-tall ` or ` md-tall ` class to the toolbar .
*
* @ usage
* < hljs lang = "html" >
* < div layout = "vertical" layout - fill >
* < md - toolbar >
*
* < div class = "md-toolbar-tools" >
* < span > My App ' s Title < / s p a n >
*
* <!-- fill up the space between left and right area -- >
* < span flex > < / s p a n >
*
* < md - button >
* Right Bar Button
* < / m d - b u t t o n >
* < / d i v >
*
* < / m d - t o o l b a r >
* < md - content >
* Hello !
* < / m d - c o n t e n t >
* < / d i v >
* < / h l j s >
*
* @ param { boolean = } scrollShrink Whether the header should shrink away as
* the user scrolls down , and reveal itself as the user scrolls up .
* Note : for scrollShrink to work , the toolbar must be a sibling of a
* ` md-content ` element , placed before it . See the scroll shrink demo .
*
*
* @ param { number = } shrinkSpeedFactor How much to change the speed of the toolbar ' s
* shrinking by . For example , if 0.25 is given then the toolbar will shrink
* at one fourth the rate at which the user scrolls down . Default 0.5 .
* /
2014-11-06 16:55:16 +01:00
function mdToolbarDirective ( $$rAF , $mdEffects , $mdUtil , $mdTheming ) {
2014-11-03 17:32:25 +01:00
return {
restrict : 'E' ,
controller : angular . noop ,
link : function ( scope , element , attr ) {
2014-11-06 16:55:16 +01:00
$mdTheming ( element ) ;
2014-11-03 17:32:25 +01:00
if ( angular . isDefined ( attr . scrollShrink ) ) {
setupScrollShrink ( ) ;
2014-10-12 19:07:47 +02:00
}
2014-09-30 12:29:53 +02:00
2014-11-03 17:32:25 +01:00
function setupScrollShrink ( ) {
// Current "y" position of scroll
var y = 0 ;
// Store the last scroll top position
var prevScrollTop = 0 ;
2014-09-30 12:29:53 +02:00
2014-11-03 17:32:25 +01:00
var shrinkSpeedFactor = attr . shrinkSpeedFactor || 0.5 ;
2014-09-30 12:29:53 +02:00
2014-11-03 17:32:25 +01:00
var toolbarHeight ;
var contentElement ;
2014-09-30 12:29:53 +02:00
2014-11-03 17:32:25 +01:00
var debouncedContentScroll = $$rAF . debounce ( onContentScroll ) ;
var debouncedUpdateHeight = $mdUtil . debounce ( updateToolbarHeight , 5 * 1000 ) ;
2014-09-30 12:29:53 +02:00
2014-11-03 17:32:25 +01:00
// Wait for $mdContentLoaded event from mdContent directive.
// If the mdContent element is a sibling of our toolbar, hook it up
// to scroll events.
scope . $on ( '$mdContentLoaded' , onMdContentLoad ) ;
2014-09-30 12:29:53 +02:00
2014-11-03 17:32:25 +01:00
function onMdContentLoad ( $event , newContentEl ) {
if ( $mdUtil . elementIsSibling ( element , newContentEl ) ) {
// unhook old content event listener if exists
if ( contentElement ) {
contentElement . off ( 'scroll' , debouncedContentScroll ) ;
}
2014-09-30 12:29:53 +02:00
2014-11-03 17:32:25 +01:00
newContentEl . on ( 'scroll' , debouncedContentScroll ) ;
newContentEl . attr ( 'scroll-shrink' , 'true' ) ;
2014-09-30 12:29:53 +02:00
2014-11-03 17:32:25 +01:00
contentElement = newContentEl ;
$$rAF ( updateToolbarHeight ) ;
}
}
2014-09-30 12:29:53 +02:00
2014-11-03 17:32:25 +01:00
function updateToolbarHeight ( ) {
toolbarHeight = element . prop ( 'offsetHeight' ) ;
// Add a negative margin-top the size of the toolbar to the content el.
// The content will start transformed down the toolbarHeight amount,
// so everything looks normal.
//
// As the user scrolls down, the content will be transformed up slowly
// to put the content underneath where the toolbar was.
contentElement . css (
'margin-top' ,
( - toolbarHeight * shrinkSpeedFactor ) + 'px'
) ;
onContentScroll ( ) ;
}
2014-09-30 12:29:53 +02:00
2014-11-03 17:32:25 +01:00
function onContentScroll ( e ) {
var scrollTop = e ? e . target . scrollTop : prevScrollTop ;
2014-09-30 12:29:53 +02:00
2014-11-03 17:32:25 +01:00
debouncedUpdateHeight ( ) ;
2014-09-30 12:29:53 +02:00
2014-11-03 17:32:25 +01:00
y = Math . min (
toolbarHeight / shrinkSpeedFactor ,
Math . max ( 0 , y + scrollTop - prevScrollTop )
) ;
2014-09-30 12:29:53 +02:00
2014-11-03 17:32:25 +01:00
element . css (
$mdEffects . TRANSFORM ,
'translate3d(0,' + ( - y * shrinkSpeedFactor ) + 'px,0)'
) ;
contentElement . css (
$mdEffects . TRANSFORM ,
'translate3d(0,' + ( ( toolbarHeight - y ) * shrinkSpeedFactor ) + 'px,0)'
) ;
2014-09-30 12:29:53 +02:00
2014-11-03 17:32:25 +01:00
prevScrollTop = scrollTop ;
}
2014-09-30 12:29:53 +02:00
2014-11-03 17:32:25 +01:00
}
2014-09-30 12:29:53 +02:00
}
2014-11-03 17:32:25 +01:00
} ;
2014-09-30 12:29:53 +02:00
2014-10-12 19:07:47 +02:00
}
2014-11-03 17:32:25 +01:00
} ) ( ) ;
2014-09-30 12:29:53 +02:00
2014-11-03 17:32:25 +01:00
( function ( ) {
/ * *
* @ ngdoc module
* @ name material . components . tooltip
* /
2014-11-06 16:55:16 +01:00
angular . module ( 'material.components.tooltip' , [
'material.core' ,
'material.services.theming'
] )
2014-11-03 17:32:25 +01:00
. directive ( 'mdTooltip' , [
'$timeout' ,
'$window' ,
'$$rAF' ,
'$document' ,
'$mdUtil' ,
2014-11-06 16:55:16 +01:00
'$mdTheming' ,
2014-11-03 17:32:25 +01:00
MdTooltipDirective
] ) ;
2014-09-30 12:29:53 +02:00
/ * *
* @ ngdoc directive
2014-11-03 17:32:25 +01:00
* @ name mdTooltip
* @ module material . components . tooltip
2014-09-30 12:29:53 +02:00
* @ description
2014-11-03 17:32:25 +01:00
* Tooltips are used to describe elements that are interactive and primarily graphical ( not textual ) .
2014-09-30 12:29:53 +02:00
*
2014-11-03 17:32:25 +01:00
* Place a ` <md-tooltip> ` as a child of the element it describes .
2014-09-30 12:29:53 +02:00
*
2014-11-03 17:32:25 +01:00
* A tooltip will activate when the user focuses , hovers over , or touches the parent .
2014-09-30 12:29:53 +02:00
*
* @ usage
* < hljs lang = "html" >
2014-11-03 17:32:25 +01:00
* < md - icon icon = "/img/icons/ic_play_arrow_24px.svg" >
* < md - tooltip >
* Play Music
* < / m d - t o o l t i p >
* < / m d - i c o n >
2014-09-30 12:29:53 +02:00
* < / h l j s >
*
2014-11-03 17:32:25 +01:00
* @ param { expression = } visible Boolean bound to whether the tooltip is
* currently visible .
2014-09-30 12:29:53 +02:00
* /
2014-11-06 16:55:16 +01:00
function MdTooltipDirective ( $timeout , $window , $$rAF , $document , $mdUtil , $mdTheming ) {
2014-11-03 17:32:25 +01:00
var TOOLTIP _SHOW _DELAY = 400 ;
var TOOLTIP _WINDOW _EDGE _SPACE = 8 ;
// We have to append tooltips to the body, because we use
2014-11-06 16:55:16 +01:00
// getBoundingClientRect() to find where to append the tooltip.
2014-11-03 17:32:25 +01:00
var tooltipParent = angular . element ( document . body ) ;
2014-09-30 12:29:53 +02:00
return {
restrict : 'E' ,
2014-10-12 19:07:47 +02:00
transclude : true ,
2014-11-03 17:32:25 +01:00
require : '^?mdContent' ,
template :
2014-11-06 16:55:16 +01:00
'<div class="md-background"></div>' +
'<div class="md-content" ng-transclude></div>' ,
2014-09-30 12:29:53 +02:00
scope : {
2014-11-03 17:32:25 +01:00
visible : '=?'
2014-09-30 12:29:53 +02:00
} ,
2014-11-03 17:32:25 +01:00
link : postLink
} ;
2014-09-30 12:29:53 +02:00
2014-11-03 17:32:25 +01:00
function postLink ( scope , element , attr , contentCtrl ) {
2014-11-06 16:55:16 +01:00
$mdTheming ( element ) ;
2014-11-03 17:32:25 +01:00
var parent = element . parent ( ) ;
2014-09-30 12:29:53 +02:00
2014-11-03 17:32:25 +01:00
// We will re-attach tooltip when visible
element . detach ( ) ;
element . attr ( 'role' , 'tooltip' ) ;
element . attr ( 'id' , attr . id || ( 'tooltip_' + $mdUtil . nextUid ( ) ) ) ;
2014-09-30 12:29:53 +02:00
2014-11-03 17:32:25 +01:00
parent . on ( 'focus mouseenter touchstart' , function ( ) {
setVisible ( true ) ;
} ) ;
parent . on ( 'blur mouseleave touchend touchcancel' , function ( ) {
// Don't hide the tooltip if the parent is still focused.
2014-11-06 16:55:16 +01:00
if ( $document . activeElement === parent [ 0 ] ) return ;
2014-11-03 17:32:25 +01:00
setVisible ( false ) ;
} ) ;
2014-09-30 12:29:53 +02:00
2014-11-03 17:32:25 +01:00
scope . $watch ( 'visible' , function ( isVisible ) {
if ( isVisible ) showTooltip ( ) ;
else hideTooltip ( ) ;
} ) ;
var debouncedOnResize = $$rAF . debounce ( onWindowResize ) ;
angular . element ( $window ) . on ( 'resize' , debouncedOnResize ) ;
function onWindowResize ( ) {
// Reposition on resize
if ( scope . visible ) positionTooltip ( ) ;
}
2014-09-30 12:29:53 +02:00
2014-11-03 17:32:25 +01:00
// Be sure to completely cleanup the element on destroy
scope . $on ( '$destroy' , function ( ) {
scope . visible = false ;
element . remove ( ) ;
angular . element ( $window ) . off ( 'resize' , debouncedOnResize ) ;
} ) ;
2014-09-30 12:29:53 +02:00
2014-11-03 17:32:25 +01:00
// *******
// Methods
// *******
2014-09-30 12:29:53 +02:00
2014-11-03 17:32:25 +01:00
// If setting visible to true, debounce to TOOLTIP_SHOW_DELAY ms
// If setting visible to false and no timeout is active, instantly hide the tooltip.
function setVisible ( value ) {
setVisible . value = ! ! value ;
if ( ! setVisible . queued ) {
if ( value ) {
setVisible . queued = true ;
$timeout ( function ( ) {
scope . visible = setVisible . value ;
setVisible . queued = false ;
} , TOOLTIP _SHOW _DELAY ) ;
} else {
$timeout ( function ( ) { scope . visible = false ; } ) ;
}
}
2014-10-12 19:07:47 +02:00
}
2014-09-30 12:29:53 +02:00
2014-11-03 17:32:25 +01:00
function showTooltip ( ) {
// Insert the element before positioning it, so we can get position
// (tooltip is hidden by default)
2014-11-06 16:55:16 +01:00
element . removeClass ( 'md-hide' ) ;
2014-11-03 17:32:25 +01:00
parent . attr ( 'aria-describedby' , element . attr ( 'id' ) ) ;
tooltipParent . append ( element ) ;
2014-09-30 12:29:53 +02:00
2014-11-03 17:32:25 +01:00
// Wait until the element has been in the dom for two frames before
// fading it in.
// Additionally, we position the tooltip twice to avoid positioning bugs
//positionTooltip();
$$rAF ( function ( ) {
2014-09-30 12:29:53 +02:00
2014-11-03 17:32:25 +01:00
$$rAF ( function ( ) {
positionTooltip ( ) ;
if ( ! scope . visible ) return ;
2014-11-06 16:55:16 +01:00
element . addClass ( 'md-show' ) ;
2014-11-03 17:32:25 +01:00
} ) ;
2014-09-30 12:29:53 +02:00
2014-10-12 19:07:47 +02:00
} ) ;
2014-09-30 12:29:53 +02:00
}
2014-11-03 17:32:25 +01:00
function hideTooltip ( ) {
2014-11-06 16:55:16 +01:00
element . removeClass ( 'md-show' ) . addClass ( 'md-hide' ) ;
2014-11-03 17:32:25 +01:00
parent . removeAttr ( 'aria-describedby' ) ;
$timeout ( function ( ) {
if ( scope . visible ) return ;
element . detach ( ) ;
} , 200 , false ) ;
}
function positionTooltip ( rerun ) {
var tipRect = element [ 0 ] . getBoundingClientRect ( ) ;
var parentRect = parent [ 0 ] . getBoundingClientRect ( ) ;
if ( contentCtrl ) {
parentRect . top += contentCtrl . $element . prop ( 'scrollTop' ) ;
parentRect . left += contentCtrl . $element . prop ( 'scrollLeft' ) ;
}
// Default to bottom position if possible
var tipDirection = 'bottom' ;
var newPosition = {
left : parentRect . left + parentRect . width / 2 - tipRect . width / 2 ,
top : parentRect . top + parentRect . height
} ;
// If element bleeds over left/right of the window, place it on the edge of the window.
newPosition . left = Math . min (
newPosition . left ,
$window . innerWidth - tipRect . width - TOOLTIP _WINDOW _EDGE _SPACE
) ;
newPosition . left = Math . max ( newPosition . left , TOOLTIP _WINDOW _EDGE _SPACE ) ;
// If element bleeds over the bottom of the window, place it above the parent.
if ( newPosition . top + tipRect . height > $window . innerHeight ) {
newPosition . top = parentRect . top - tipRect . height ;
tipDirection = 'top' ;
}
element . css ( { top : newPosition . top + 'px' , left : newPosition . left + 'px' } ) ;
// Tell the CSS the size of this tooltip, as a multiple of 32.
element . attr ( 'width-32' , Math . ceil ( tipRect . width / 32 ) ) ;
2014-11-06 16:55:16 +01:00
element . attr ( 'md-direction' , tipDirection ) ;
2014-11-03 17:32:25 +01:00
}
2014-10-12 19:07:47 +02:00
}
2014-11-03 17:32:25 +01:00
2014-09-30 12:29:53 +02:00
}
2014-11-03 17:32:25 +01:00
} ) ( ) ;
2014-09-30 12:29:53 +02:00
2014-11-03 17:32:25 +01:00
( function ( ) {
2014-09-30 12:29:53 +02:00
/ * *
* @ ngdoc module
2014-11-03 17:32:25 +01:00
* @ name material . components . whiteframe
2014-09-30 12:29:53 +02:00
* /
2014-11-03 17:32:25 +01:00
angular . module ( 'material.components.whiteframe' , [ ] ) ;
} ) ( ) ;
( function ( ) {
angular . module ( 'material.services.aria' , [ ] )
. service ( '$mdAria' , [
2014-11-06 16:55:16 +01:00
'$$rAF' ,
2014-11-03 17:32:25 +01:00
'$log' ,
AriaService
] ) ;
2014-11-06 16:55:16 +01:00
function AriaService ( $$rAF , $log ) {
var messageTemplate = 'ARIA: Attribute "%s", required for accessibility, is missing on "%s"' ;
2014-11-03 17:32:25 +01:00
var defaultValueTemplate = 'Default value was set: %s="%s".' ;
2014-09-30 12:29:53 +02:00
return {
2014-11-03 17:32:25 +01:00
expect : expectAttribute ,
2014-09-30 12:29:53 +02:00
} ;
2014-11-03 17:32:25 +01:00
/ * *
* Check if expected ARIA has been specified on the target element
* @ param element
* @ param attrName
2014-11-06 16:55:16 +01:00
* @ param copyElementText
* @ param { optional } defaultValue
2014-11-03 17:32:25 +01:00
* /
2014-11-06 16:55:16 +01:00
function expectAttribute ( element , attrName , copyElementText , defaultValue ) {
2014-10-12 19:07:47 +02:00
2014-11-06 16:55:16 +01:00
$$rAF ( function ( ) {
var node = element [ 0 ] ;
if ( ! node . hasAttribute ( attrName ) ) {
var hasDefault ;
if ( copyElementText === true ) {
if ( ! defaultValue ) defaultValue = element . text ( ) . trim ( ) ;
hasDefault = angular . isDefined ( defaultValue ) && defaultValue . length ;
}
if ( hasDefault ) {
defaultValue = String ( defaultValue ) . trim ( ) ;
element . attr ( attrName , defaultValue ) ;
} else {
$log . warn ( messageTemplate , attrName , node ) ;
$log . warn ( node ) ;
}
2014-11-03 17:32:25 +01:00
}
2014-11-06 16:55:16 +01:00
} ) ;
2014-11-03 17:32:25 +01:00
}
/ * *
* Gets the tag definition from a node ' s outerHTML
* @ example getTagString (
* '<md-button foo="bar">Hello</md-button>'
* ) // => '<md-button foo="bar">'
* /
function getTagString ( node ) {
var html = node . outerHTML ;
var closingIndex = html . indexOf ( '>' ) ;
return html . substring ( 0 , closingIndex + 1 ) ;
}
}
} ) ( ) ;
( function ( ) {
angular . module ( 'material.services.attrBind' , [
] )
. factory ( '$attrBind' , [
'$parse' ,
'$interpolate' ,
MdAttrBind
] ) ;
2014-09-30 12:29:53 +02:00
2014-10-12 19:07:47 +02:00
/ * *
2014-11-03 17:32:25 +01:00
* This service allows directives to easily databind attributes to private scope properties .
2014-10-12 19:07:47 +02:00
*
2014-11-03 17:32:25 +01:00
* @ private
2014-10-12 19:07:47 +02:00
* /
2014-11-03 17:32:25 +01:00
function MdAttrBind ( $parse , $interpolate ) {
var LOCAL _REGEXP = /^\s*([@=&])(\??)\s*(\w*)\s*$/ ;
2014-09-30 12:29:53 +02:00
2014-11-03 17:32:25 +01:00
return function ( scope , attrs , bindDefinition , bindDefaults ) {
angular . forEach ( bindDefinition || { } , function ( definition , scopeName ) {
//Adapted from angular.js $compile
var match = definition . match ( LOCAL _REGEXP ) || [ ] ,
attrName = match [ 3 ] || scopeName ,
mode = match [ 1 ] , // @, =, or &
parentGet ,
unWatchFn ;
2014-09-30 12:29:53 +02:00
2014-11-03 17:32:25 +01:00
switch ( mode ) {
case '@' : // One-way binding from attribute into scope
2014-09-30 12:29:53 +02:00
2014-11-03 17:32:25 +01:00
attrs . $observe ( attrName , function ( value ) {
scope [ scopeName ] = value ;
} ) ;
attrs . $$observers [ attrName ] . $$scope = scope ;
2014-09-30 12:29:53 +02:00
2014-11-03 17:32:25 +01:00
if ( ! bypassWithDefaults ( attrName , scopeName ) ) {
// we trigger an interpolation to ensure
// the value is there for use immediately
scope [ scopeName ] = $interpolate ( attrs [ attrName ] ) ( scope ) ;
}
break ;
2014-09-30 12:29:53 +02:00
2014-11-03 17:32:25 +01:00
case '=' : // Two-way binding...
2014-09-30 12:29:53 +02:00
2014-11-03 17:32:25 +01:00
if ( ! bypassWithDefaults ( attrName , scopeName ) ) {
// Immediate evaluation
scope [ scopeName ] = ( attrs [ attrName ] === "" ) ? true : scope . $eval ( attrs [ attrName ] ) ;
2014-09-30 12:29:53 +02:00
2014-11-03 17:32:25 +01:00
// Data-bind attribute to scope (incoming) and
// auto-release watcher when scope is destroyed
2014-09-30 12:29:53 +02:00
2014-11-03 17:32:25 +01:00
unWatchFn = scope . $watch ( attrs [ attrName ] , function ( value ) {
scope [ scopeName ] = value ;
} ) ;
scope . $on ( '$destroy' , unWatchFn ) ;
}
2014-09-30 12:29:53 +02:00
2014-11-03 17:32:25 +01:00
break ;
2014-09-30 12:29:53 +02:00
2014-11-03 17:32:25 +01:00
case '&' : // execute an attribute-defined expression in the context of the parent scope
2014-09-30 12:29:53 +02:00
2014-11-03 17:32:25 +01:00
if ( ! bypassWithDefaults ( attrName , scopeName , angular . noop ) ) {
/* jshint -W044 */
if ( attrs [ attrName ] && attrs [ attrName ] . match ( RegExp ( scopeName + '\(.*?\)' ) ) ) {
throw new Error ( '& expression binding "' + scopeName + '" looks like it will recursively call "' +
attrs [ attrName ] + '" and cause a stack overflow! Please choose a different scopeName.' ) ;
}
2014-09-30 12:29:53 +02:00
2014-11-03 17:32:25 +01:00
parentGet = $parse ( attrs [ attrName ] ) ;
scope [ scopeName ] = function ( locals ) {
return parentGet ( scope , locals ) ;
} ;
}
2014-09-30 12:29:53 +02:00
2014-11-03 17:32:25 +01:00
break ;
2014-09-30 12:29:53 +02:00
}
2014-11-03 17:32:25 +01:00
} ) ;
2014-09-30 12:29:53 +02:00
2014-11-03 17:32:25 +01:00
/ * *
* Optional fallback value if attribute is not specified on element
* @ param scopeName
* /
function bypassWithDefaults ( attrName , scopeName , defaultVal ) {
if ( ! angular . isDefined ( attrs [ attrName ] ) ) {
var hasDefault = bindDefaults && bindDefaults . hasOwnProperty ( scopeName ) ;
scope [ scopeName ] = hasDefault ? bindDefaults [ scopeName ] : defaultVal ;
return true ;
}
return false ;
}
2014-09-30 12:29:53 +02:00
2014-11-03 17:32:25 +01:00
} ;
}
} ) ( ) ;
2014-09-30 12:29:53 +02:00
2014-11-03 17:32:25 +01:00
( function ( ) {
/ *
* @ ngdoc module
* @ name material . services . compiler
* @ description compiler service
* /
angular . module ( 'material.services.compiler' , [
] )
. service ( '$mdCompiler' , [
'$q' ,
'$http' ,
'$injector' ,
'$compile' ,
'$controller' ,
'$templateCache' ,
mdCompilerService
] ) ;
2014-09-30 12:29:53 +02:00
2014-11-03 17:32:25 +01:00
function mdCompilerService ( $q , $http , $injector , $compile , $controller , $templateCache ) {
2014-09-30 12:29:53 +02:00
2014-11-03 17:32:25 +01:00
/ *
* @ ngdoc service
* @ name $mdCompiler
* @ module material . services . compiler
* @ description
* The $mdCompiler service is an abstraction of angular ' s compiler , that allows the developer
* to easily compile an element with a templateUrl , controller , and locals .
*
* @ usage
* < hljs lang = "js" >
* $mdCompiler . compile ( {
* templateUrl : 'modal.html' ,
* controller : 'ModalCtrl' ,
* locals : {
* modal : myModalInstance ;
* }
* } ) . then ( function ( compileData ) {
* compileData . element ; // modal.html's template in an element
* compileData . link ( myScope ) ; //attach controller & scope to element
* } ) ;
* < / h l j s >
* /
2014-09-30 12:29:53 +02:00
2014-11-03 17:32:25 +01:00
/ *
* @ ngdoc method
* @ name $mdCompiler # compile
* @ description A helper to compile an HTML template / templateUrl with a given controller ,
* locals , and scope .
* @ param { object } options An options object , with the following properties :
*
* - ` controller ` - ` {(string=|function()=} ` Controller fn that should be associated with
* newly created scope or the name of a registered controller if passed as a string .
* - ` controllerAs ` - ` {string=} ` A controller alias name . If present the controller will be
* published to scope under the ` controllerAs ` name .
* - ` template ` - ` {string=} ` An html template as a string .
* - ` templateUrl ` - ` {string=} ` A path to an html template .
* - ` transformTemplate ` - ` {function(template)=} ` A function which transforms the template after
* it is loaded . It will be given the template string as a parameter , and should
* return a a new string representing the transformed template .
* - ` resolve ` - ` {Object.<string, function>=} ` - An optional map of dependencies which should
* be injected into the controller . If any of these dependencies are promises , the compiler
* will wait for them all to be resolved , or if one is rejected before the controller is
* instantiated ` compile() ` will fail . .
* * ` key ` - ` {string} ` : a name of a dependency to be injected into the controller .
* * ` factory ` - ` {string|function} ` : If ` string ` then it is an alias for a service .
* Otherwise if function , then it is injected and the return value is treated as the
* dependency . If the result is a promise , it is resolved before its value is
* injected into the controller .
*
* @ returns { object = } promise A promise , which will be resolved with a ` compileData ` object .
* ` compileData ` has the following properties :
*
* - ` element ` - ` {element} ` : an uncompiled element matching the provided template .
* - ` link ` - ` {function(scope)} ` : A link function , which , when called , will compile
* the element and instantiate the provided controller ( if given ) .
* - ` locals ` - ` {object} ` : The locals which will be passed into the controller once ` link ` is
* called .
* /
this . compile = function ( options ) {
var templateUrl = options . templateUrl ;
var template = options . template || '' ;
var controller = options . controller ;
var controllerAs = options . controllerAs ;
var resolve = options . resolve || { } ;
var locals = options . locals || { } ;
var transformTemplate = options . transformTemplate || angular . identity ;
2014-09-30 12:29:53 +02:00
2014-11-03 17:32:25 +01:00
// Take resolve values and invoke them.
// Resolves can either be a string (value: 'MyRegisteredAngularConst'),
// or an invokable 'factory' of sorts: (value: function ValueGetter($dependency) {})
angular . forEach ( resolve , function ( value , key ) {
if ( angular . isString ( value ) ) {
resolve [ key ] = $injector . get ( value ) ;
} else {
resolve [ key ] = $injector . invoke ( value ) ;
}
} ) ;
//Add the locals, which are just straight values to inject
//eg locals: { three: 3 }, will inject three into the controller
angular . extend ( resolve , locals ) ;
2014-09-30 12:29:53 +02:00
2014-11-03 17:32:25 +01:00
if ( templateUrl ) {
resolve . $template = $http . get ( templateUrl , { cache : $templateCache } )
. then ( function ( response ) {
return response . data ;
} ) ;
} else {
resolve . $template = $q . when ( template ) ;
}
2014-09-30 12:29:53 +02:00
2014-11-03 17:32:25 +01:00
// Wait for all the resolves to finish if they are promises
return $q . all ( resolve ) . then ( function ( locals ) {
2014-09-30 12:29:53 +02:00
2014-11-03 17:32:25 +01:00
var template = transformTemplate ( locals . $template ) ;
var element = angular . element ( '<div>' ) . html ( template ) . contents ( ) ;
var linkFn = $compile ( element ) ;
2014-09-30 12:29:53 +02:00
2014-11-03 17:32:25 +01:00
//Return a linking function that can be used later when the element is ready
return {
locals : locals ,
element : element ,
link : function link ( scope ) {
locals . $scope = scope ;
2014-09-30 12:29:53 +02:00
2014-11-03 17:32:25 +01:00
//Instantiate controller if it exists, because we have scope
if ( controller ) {
var ctrl = $controller ( controller , locals ) ;
//See angular-route source for this logic
element . data ( '$ngControllerController' , ctrl ) ;
element . children ( ) . data ( '$ngControllerController' , ctrl ) ;
2014-09-30 12:29:53 +02:00
2014-11-03 17:32:25 +01:00
if ( controllerAs ) {
scope [ controllerAs ] = ctrl ;
}
}
2014-09-30 12:29:53 +02:00
2014-11-03 17:32:25 +01:00
return linkFn ( scope ) ;
2014-09-30 12:29:53 +02:00
}
2014-11-03 17:32:25 +01:00
} ;
} ) ;
2014-09-30 12:29:53 +02:00
} ;
}
2014-11-03 17:32:25 +01:00
} ) ( ) ;
2014-09-30 12:29:53 +02:00
2014-11-03 17:32:25 +01:00
( function ( ) {
/ *
2014-10-12 19:07:47 +02:00
* @ ngdoc module
2014-11-03 17:32:25 +01:00
* @ name material . services . interimElement
* @ description InterimElement
2014-10-12 19:07:47 +02:00
* /
2014-11-03 17:32:25 +01:00
angular . module ( 'material.services.interimElement' , [
2014-11-06 16:55:16 +01:00
'material.services.compiler' ,
'material.services.theming'
2014-11-03 17:32:25 +01:00
] )
. factory ( '$$interimElement' , [
'$q' ,
'$rootScope' ,
2014-10-12 19:07:47 +02:00
'$timeout' ,
2014-11-03 17:32:25 +01:00
'$rootElement' ,
'$animate' ,
'$mdCompiler' ,
2014-11-06 16:55:16 +01:00
'$mdTheming' ,
2014-11-03 17:32:25 +01:00
InterimElementFactory
2014-10-12 19:07:47 +02:00
] ) ;
2014-11-03 17:32:25 +01:00
/ *
* @ ngdoc service
* @ name $$interimElement
*
2014-10-12 19:07:47 +02:00
* @ description
*
2014-11-03 17:32:25 +01:00
* Factory that contructs ` $ $ interimElement. $ service ` services .
* Used internally in material design for elements that appear on screen temporarily .
* The service provides a promise - like API for interacting with the temporary
* elements .
2014-10-12 19:07:47 +02:00
*
2014-11-03 17:32:25 +01:00
* ` ` ` js
* app . service ( '$mdToast' , function ( $$interimElement ) {
* var $mdToast = $$interimElement ( toastDefaultOptions ) ;
* return $mdToast ;
* } ) ;
* ` ` `
* @ param { object = } defaultOptions Options used by default for the ` show ` method on the service .
2014-10-12 19:07:47 +02:00
*
2014-11-03 17:32:25 +01:00
* @ returns { $$interimElement . $service }
2014-10-12 19:07:47 +02:00
*
* /
2014-11-06 16:55:16 +01:00
function InterimElementFactory ( $q , $rootScope , $timeout , $rootElement , $animate , $mdCompiler , $mdTheming ) {
2014-10-12 19:07:47 +02:00
2014-11-03 17:32:25 +01:00
return function createInterimElementService ( defaults ) {
2014-10-12 19:07:47 +02:00
2014-11-03 17:32:25 +01:00
/ *
* @ ngdoc service
* @ name $$interimElement . $service
*
* @ description
* A service used to control inserting and removing an element into the DOM .
*
* /
2014-10-12 19:07:47 +02:00
2014-11-03 17:32:25 +01:00
var stack = [ ] ;
2014-10-12 19:07:47 +02:00
2014-11-03 17:32:25 +01:00
defaults = angular . extend ( {
onShow : function ( scope , $el , options ) {
return $animate . enter ( $el , options . parent ) ;
} ,
onRemove : function ( scope , $el , options ) {
return $animate . leave ( $el ) ;
} ,
} , defaults || { } ) ;
2014-10-12 19:07:47 +02:00
2014-11-03 17:32:25 +01:00
var service ;
return service = {
show : show ,
hide : hide ,
cancel : cancel
} ;
2014-10-12 19:07:47 +02:00
2014-11-03 17:32:25 +01:00
/ *
* @ ngdoc method
* @ name $$interimElement . $service # show
* @ kind function
*
* @ description
* Compiles and inserts an element into the DOM .
*
* @ param { Object } options Options object to compile with .
*
* @ returns { Promise } Promise that will resolve when the service
* has ` #close() ` or ` #cancel() ` called .
*
* /
function show ( options ) {
if ( stack . length ) {
service . hide ( ) ;
2014-10-12 19:07:47 +02:00
}
2014-11-03 17:32:25 +01:00
var interimElement = new InterimElement ( options ) ;
stack . push ( interimElement ) ;
return interimElement . show ( ) . then ( function ( ) {
return interimElement . deferred . promise ;
2014-10-12 19:07:47 +02:00
} ) ;
}
2014-11-03 17:32:25 +01:00
/ *
* @ ngdoc method
* @ name $$interimElement . $service # hide
* @ kind function
*
* @ description
* Removes the ` $ interimElement ` from the DOM and resolves the promise returned from ` show `
*
* @ param { * } resolveParam Data to resolve the promise with
*
* @ returns undefined data that resolves after the element has been removed .
*
* /
function hide ( success ) {
var interimElement = stack . shift ( ) ;
interimElement && interimElement . remove ( ) . then ( function ( ) {
interimElement . deferred . resolve ( success ) ;
} ) ;
2014-10-12 19:07:47 +02:00
}
2014-11-03 17:32:25 +01:00
/ *
* @ ngdoc method
* @ name $$interimElement . $service # cancel
* @ kind function
*
* @ description
* Removes the ` $ interimElement ` from the DOM and rejects the promise returned from ` show `
*
* @ param { * } reason Data to reject the promise with
*
* @ returns undefined
*
* /
function cancel ( reason ) {
var interimElement = stack . shift ( ) ;
interimElement && interimElement . remove ( ) . then ( function ( ) {
interimElement . deferred . reject ( reason ) ;
} ) ;
}
2014-10-12 19:07:47 +02:00
2014-11-03 17:32:25 +01:00
/ *
* Internal Interim Element Object
* Used internally to manage the DOM element and related data
* /
function InterimElement ( options ) {
var self ;
var hideTimeout , element ;
2014-10-12 19:07:47 +02:00
2014-11-03 17:32:25 +01:00
options = options || { } ;
2014-10-12 19:07:47 +02:00
2014-11-03 17:32:25 +01:00
options = angular . extend ( {
scope : options . scope || $rootScope . $new ( options . isolateScope )
} , defaults , options ) ;
2014-10-12 19:07:47 +02:00
2014-11-03 17:32:25 +01:00
return self = {
options : options ,
deferred : $q . defer ( ) ,
show : function ( ) {
return $mdCompiler . compile ( options ) . then ( function ( compiledData ) {
// Search for parent at insertion time, if not specified
if ( ! options . parent ) {
options . parent = $rootElement . find ( 'body' ) ;
if ( ! options . parent . length ) options . parent = $rootElement ;
}
element = compiledData . link ( options . scope ) ;
2014-11-06 16:55:16 +01:00
if ( options . themable ) $mdTheming ( element ) ;
2014-11-03 17:32:25 +01:00
var ret = options . onShow ( options . scope , element , options ) ;
return $q . when ( ret )
. then ( startHideTimeout ) ;
2014-10-12 19:07:47 +02:00
2014-11-03 17:32:25 +01:00
function startHideTimeout ( ) {
if ( options . hideDelay ) {
hideTimeout = $timeout ( service . hide , options . hideDelay ) ;
}
}
} ) ;
} ,
cancelTimeout : function ( ) {
if ( hideTimeout ) {
$timeout . cancel ( hideTimeout ) ;
hideTimeout = undefined ;
}
} ,
remove : function ( ) {
self . cancelTimeout ( ) ;
var ret = options . onRemove ( options . scope , element , options ) ;
return $q . when ( ret ) . then ( function ( ) {
options . scope . $destroy ( ) ;
} ) ;
}
} ;
}
} ;
2014-10-12 19:07:47 +02:00
}
2014-11-03 17:32:25 +01:00
} ) ( ) ;
2014-09-30 12:29:53 +02:00
2014-11-06 16:55:16 +01:00
( function ( ) {
angular . module ( 'material.services.media' , [
'material.core'
] )
. factory ( '$mdMedia' , [
'$window' ,
'$mdUtil' ,
'$timeout' ,
mdMediaFactory
] ) ;
function mdMediaFactory ( $window , $mdUtil , $timeout ) {
var cache = $mdUtil . cacheFactory ( '$mdMedia' , { capacity : 15 } ) ;
var presets = {
sm : '(min-width: 600px)' ,
md : '(min-width: 960px)' ,
lg : '(min-width: 1200px)'
} ;
angular . element ( $window ) . on ( 'resize' , updateAll ) ;
return $mdMedia ;
function $mdMedia ( query ) {
query = validate ( query ) ;
var result ;
if ( ! angular . isDefined ( result = cache . get ( query ) ) ) {
return add ( query ) ;
}
return result ;
}
function validate ( query ) {
return presets [ query ] || (
query . charAt ( 0 ) != '(' ? ( '(' + query + ')' ) : query
) ;
}
function add ( query ) {
return cache . put ( query , ! ! $window . matchMedia ( query ) . matches ) ;
}
function updateAll ( ) {
var keys = cache . keys ( ) ;
if ( keys . length ) {
for ( var i = 0 , ii = keys . length ; i < ii ; i ++ ) {
cache . put ( keys [ i ] , ! ! $window . matchMedia ( keys [ i ] ) . matches ) ;
}
// trigger an $digest()
$timeout ( angular . noop ) ;
}
}
}
} ) ( ) ;
2014-11-03 17:32:25 +01:00
( function ( ) {
/ *
2014-09-30 12:29:53 +02:00
* @ ngdoc module
2014-11-03 17:32:25 +01:00
* @ name material . services . registry
*
* @ description
* A component registry system for accessing various component instances in an app .
2014-09-30 12:29:53 +02:00
* /
2014-11-03 17:32:25 +01:00
angular . module ( 'material.services.registry' , [
2014-09-30 12:29:53 +02:00
] )
2014-11-03 17:32:25 +01:00
. factory ( '$mdComponentRegistry' , [
'$log' ,
mdComponentRegistry
] ) ;
2014-09-30 12:29:53 +02:00
2014-11-03 17:32:25 +01:00
/ *
* @ ngdoc service
* @ name $mdComponentRegistry
* @ module material . services . registry
2014-09-30 12:29:53 +02:00
*
* @ description
2014-11-03 17:32:25 +01:00
* $mdComponentRegistry enables the user to interact with multiple instances of
* certain complex components in a running app .
2014-09-30 12:29:53 +02:00
* /
2014-11-03 17:32:25 +01:00
function mdComponentRegistry ( $log ) {
var instances = [ ] ;
2014-09-30 12:29:53 +02:00
return {
2014-11-03 17:32:25 +01:00
/ * *
* Used to print an error when an instance for a handle isn ' t found .
* /
notFoundError : function ( handle ) {
$log . error ( 'No instance found for handle' , handle ) ;
} ,
/ * *
* Return all registered instances as an array .
* /
getInstances : function ( ) {
return instances ;
} ,
/ * *
* Get a registered instance .
* @ param handle the String handle to look up for a registered instance .
* /
get : function ( handle ) {
var i , j , instance ;
for ( i = 0 , j = instances . length ; i < j ; i ++ ) {
instance = instances [ i ] ;
if ( instance . $$mdHandle === handle ) {
return instance ;
}
}
return null ;
} ,
/ * *
* Register an instance .
* @ param instance the instance to register
* @ param handle the handle to identify the instance under .
* /
register : function ( instance , handle ) {
instance . $$mdHandle = handle ;
instances . push ( instance ) ;
return function deregister ( ) {
var index = instances . indexOf ( instance ) ;
if ( index !== - 1 ) {
instances . splice ( index , 1 ) ;
}
} ;
}
}
2014-09-30 12:29:53 +02:00
}
2014-11-03 17:32:25 +01:00
} ) ( ) ;
2014-11-06 16:55:16 +01:00
( function ( ) {
/ *
* @ ngdoc module
* @ name material . services . theming
* @ description InterimElement
* /
angular . module ( 'material.services.theming' , [
] )
. directive ( 'mdTheme' , [
'$interpolate' ,
ThemingDirective
] )
. directive ( 'mdThemable' , [
'$mdTheming' ,
ThemableDirective
] )
. provider ( '$mdTheming' , [
Theming
] ) ;
/ *
* @ ngdoc provider
* @ name $mdTheming
*
* @ description
*
* Provider that makes an element apply theming related classes to itself .
*
* ` ` ` js
* app . directive ( 'myFancyDirective' , function ( $mdTheming ) {
* return {
* restrict : 'e' ,
* link : function ( scope , el , attrs ) {
* $mdTheming ( el ) ;
* }
* } ;
* } ) ;
* ` ` `
* @ param { el = } element to apply theming to
*
* @ returns { $$interimElement . $service }
*
* /
function Theming ( $injector ) {
var defaultTheme = 'default' ;
return {
setDefaultTheme : function ( theme ) {
defaultTheme = theme ;
} ,
$get : [ '$rootElement' , '$rootScope' , ThemingService ]
} ;
function ThemingService ( $rootElement , $rootScope ) {
applyTheme . inherit = function ( el , parent ) {
var ctrl = parent . controller ( 'mdTheme' ) ;
if ( angular . isDefined ( el . attr ( 'md-theme-watch' ) ) ) {
var deregisterWatch = $rootScope . $watch ( function ( ) {
return ctrl && ctrl . $mdTheme || defaultTheme ;
} , changeTheme ) ;
el . on ( '$destroy' , deregisterWatch ) ;
} else {
var theme = ctrl && ctrl . $mdTheme || defaultTheme ;
changeTheme ( theme ) ;
}
function changeTheme ( theme ) {
var oldTheme = el . data ( '$mdThemeName' ) ;
if ( oldTheme ) el . removeClass ( 'md-' + oldTheme + '-theme' ) ;
el . addClass ( 'md-' + theme + '-theme' ) ;
el . data ( '$mdThemeName' , theme ) ;
}
} ;
return applyTheme ;
function applyTheme ( scope , el ) {
// Allow us to be invoked via a linking function signature.
if ( el === undefined ) {
el = scope ;
scope = undefined ;
}
if ( scope === undefined ) {
scope = $rootScope ;
}
applyTheme . inherit ( el , el ) ;
}
}
}
function ThemingDirective ( $interpolate ) {
return {
priority : 100 ,
link : {
pre : function ( scope , el , attrs ) {
var ctrl = {
$setTheme : function ( theme ) {
ctrl . $mdTheme = theme ;
}
} ;
el . data ( '$mdThemeController' , ctrl ) ;
ctrl . $setTheme ( $interpolate ( attrs . mdTheme ) ( scope ) ) ;
attrs . $observe ( 'mdTheme' , ctrl . $setTheme ) ;
}
}
} ;
}
function ThemableDirective ( $mdTheming ) {
return $mdTheming ;
}
} ) ( ) ;
2014-11-03 17:32:25 +01:00
( function ( ) {
2014-09-30 12:29:53 +02:00
/ * *
2014-11-03 17:32:25 +01:00
* Conditionally configure ink bar animations when the
* tab selection changes . If ` nobar ` then do not show the
* bar nor animate .
2014-09-30 12:29:53 +02:00
* /
2014-11-03 17:32:25 +01:00
angular . module ( 'material.components.tabs' )
. directive ( 'mdTabsInkBar' , [
'$mdEffects' ,
'$window' ,
'$$rAF' ,
'$timeout' ,
MdTabInkDirective
2014-10-12 19:07:47 +02:00
] ) ;
2014-09-30 12:29:53 +02:00
2014-11-03 17:32:25 +01:00
function MdTabInkDirective ( $mdEffects , $window , $$rAF , $timeout ) {
2014-10-12 19:07:47 +02:00
2014-09-30 12:29:53 +02:00
return {
restrict : 'E' ,
2014-11-03 17:32:25 +01:00
require : [ '^?nobar' , '^mdTabs' ] ,
link : postLink
2014-10-12 19:07:47 +02:00
} ;
2014-11-03 17:32:25 +01:00
function postLink ( scope , element , attr , ctrls ) {
var nobar = ctrls [ 0 ] ;
var tabsCtrl = ctrls [ 1 ] ;
2014-10-12 19:07:47 +02:00
2014-11-03 17:32:25 +01:00
if ( nobar ) return ;
2014-10-12 19:07:47 +02:00
2014-11-03 17:32:25 +01:00
var debouncedUpdateBar = $$rAF . debounce ( updateBar ) ;
2014-10-12 19:07:47 +02:00
2014-11-03 17:32:25 +01:00
scope . $watch ( tabsCtrl . selected , updateBar ) ;
scope . $on ( '$mdTabsChanged' , debouncedUpdateBar ) ;
scope . $on ( '$mdTabsPaginationChanged' , debouncedUpdateBar ) ;
angular . element ( $window ) . on ( 'resize' , onWindowResize ) ;
2014-10-12 19:07:47 +02:00
2014-11-03 17:32:25 +01:00
function onWindowResize ( ) {
debouncedUpdateBar ( ) ;
$timeout ( debouncedUpdateBar , 100 , false ) ;
2014-10-12 19:07:47 +02:00
}
2014-11-03 17:32:25 +01:00
scope . $on ( '$destroy' , function ( ) {
angular . element ( $window ) . off ( 'resize' , onWindowResize ) ;
} ) ;
2014-10-12 19:07:47 +02:00
2014-11-03 17:32:25 +01:00
function updateBar ( ) {
var selectedElement = tabsCtrl . selected ( ) && tabsCtrl . selected ( ) . element ;
2014-10-12 19:07:47 +02:00
2014-11-03 17:32:25 +01:00
if ( ! selectedElement || tabsCtrl . count ( ) < 2 ) {
element . css ( {
display : 'none' ,
width : '0px'
} ) ;
} else {
var width = selectedElement . prop ( 'offsetWidth' ) ;
var left = selectedElement . prop ( 'offsetLeft' ) + ( tabsCtrl . $$pagingOffset || 0 ) ;
2014-10-12 19:07:47 +02:00
2014-11-03 17:32:25 +01:00
element . css ( {
display : width > 0 ? 'block' : 'none' ,
width : width + 'px'
} ) ;
element . css ( $mdEffects . TRANSFORM , 'translate3d(' + left + 'px,0,0)' ) ;
}
}
2014-10-12 19:07:47 +02:00
}
2014-11-03 17:32:25 +01:00
}
2014-10-12 19:07:47 +02:00
} ) ( ) ;
2014-11-03 17:32:25 +01:00
( function ( ) {
2014-10-12 19:07:47 +02:00
2014-11-03 17:32:25 +01:00
angular . module ( 'material.components.tabs' )
2014-10-12 19:07:47 +02:00
2014-11-03 17:32:25 +01:00
. directive ( 'mdTabsPagination' , [
'$mdEffects' ,
'$window' ,
'$$rAF' ,
'$$q' ,
'$timeout' ,
TabPaginationDirective
] ) ;
2014-10-12 19:07:47 +02:00
2014-11-03 17:32:25 +01:00
function TabPaginationDirective ( $mdEffects , $window , $$rAF , $$q , $timeout ) {
// TODO allow configuration of TAB_MIN_WIDTH
// Must match tab min-width rule in _tabs.scss
var TAB _MIN _WIDTH = 8 * 12 ;
// Must match (2 * width of paginators) in scss
var PAGINATORS _WIDTH = ( 8 * 4 ) * 2 ;
2014-10-12 19:07:47 +02:00
return {
2014-11-03 17:32:25 +01:00
restrict : 'A' ,
require : '^mdTabs' ,
link : postLink
2014-10-12 19:07:47 +02:00
} ;
2014-11-03 17:32:25 +01:00
function postLink ( scope , element , attr , tabsCtrl ) {
2014-10-12 19:07:47 +02:00
2014-11-03 17:32:25 +01:00
var tabsParent = element . children ( ) ;
var state = scope . pagination = {
page : - 1 ,
active : false ,
clickNext : function ( ) { userChangePage ( + 1 ) ; } ,
clickPrevious : function ( ) { userChangePage ( - 1 ) ; }
} ;
2014-10-12 19:07:47 +02:00
2014-11-03 17:32:25 +01:00
updatePagination ( ) ;
var debouncedUpdatePagination = $$rAF . debounce ( updatePagination ) ;
2014-10-12 19:07:47 +02:00
2014-11-03 17:32:25 +01:00
scope . $on ( '$mdTabsChanged' , debouncedUpdatePagination ) ;
angular . element ( $window ) . on ( 'resize' , debouncedUpdatePagination ) ;
2014-10-12 19:07:47 +02:00
2014-11-03 17:32:25 +01:00
// Listen to focus events bubbling up from md-tab elements
tabsParent . on ( 'focusin' , onTabsFocusIn ) ;
2014-10-12 19:07:47 +02:00
2014-11-03 17:32:25 +01:00
scope . $on ( '$destroy' , function ( ) {
angular . element ( $window ) . off ( 'resize' , debouncedUpdatePagination ) ;
tabsParent . off ( 'focusin' , onTabsFocusIn ) ;
} ) ;
2014-10-12 19:07:47 +02:00
2014-11-03 17:32:25 +01:00
scope . $watch ( tabsCtrl . selected , onSelectedTabChange ) ;
2014-10-12 19:07:47 +02:00
2014-11-03 17:32:25 +01:00
// Allows pagination through focus change.
function onTabsFocusIn ( ev ) {
if ( ! state . active ) return ;
2014-10-12 19:07:47 +02:00
2014-11-03 17:32:25 +01:00
var tab = angular . element ( ev . target ) . controller ( 'mdTab' ) ;
var pageIndex = getPageForTab ( tab ) ;
if ( pageIndex !== state . page ) {
// If the focused element is on a new page, don't focus yet.
tab . element . blur ( ) ;
// Go to the new page, wait for the page transition to end, then focus.
setPage ( pageIndex ) . then ( function ( ) {
tab . element . focus ( ) ;
} ) ;
2014-10-12 19:07:47 +02:00
}
2014-11-03 17:32:25 +01:00
}
2014-10-12 19:07:47 +02:00
2014-11-03 17:32:25 +01:00
function onSelectedTabChange ( selectedTab ) {
if ( ! selectedTab ) return ;
if ( state . active ) {
var selectedTabPage = getPageForTab ( selectedTab ) ;
setPage ( selectedTabPage ) ;
} else {
debouncedUpdatePagination ( ) ;
}
2014-10-12 19:07:47 +02:00
}
2014-11-03 17:32:25 +01:00
// Called when page is changed by a user action (click)
function userChangePage ( increment ) {
var newPage = state . page + increment ;
var newTab ;
if ( ! tabsCtrl . selected ( ) || getPageForTab ( tabsCtrl . selected ( ) ) !== newPage ) {
var startIndex ;
if ( increment < 0 ) {
// If going backward, select the previous available tab, starting from
// the first item on the page after newPage.
startIndex = ( newPage + 1 ) * state . itemsPerPage ;
newTab = tabsCtrl . previous ( tabsCtrl . itemAt ( startIndex ) ) ;
} else {
// If going forward, select the next available tab, starting with the
// last item before newPage.
startIndex = ( newPage * state . itemsPerPage ) - 1 ;
newTab = tabsCtrl . next ( tabsCtrl . itemAt ( startIndex ) ) ;
}
}
setPage ( newPage ) . then ( function ( ) {
newTab && newTab . element . focus ( ) ;
} ) ;
newTab && tabsCtrl . select ( newTab ) ;
2014-10-12 19:07:47 +02:00
}
2014-11-03 17:32:25 +01:00
function updatePagination ( ) {
var tabs = element . find ( 'md-tab' ) ;
var tabsWidth = element . parent ( ) . prop ( 'clientWidth' ) - PAGINATORS _WIDTH ;
2014-10-12 19:07:47 +02:00
2014-11-03 17:32:25 +01:00
var needPagination = tabsWidth && TAB _MIN _WIDTH * tabsCtrl . count ( ) > tabsWidth ;
var paginationToggled = needPagination !== state . active ;
2014-10-12 19:07:47 +02:00
2014-11-03 17:32:25 +01:00
state . active = needPagination ;
2014-10-12 19:07:47 +02:00
2014-11-03 17:32:25 +01:00
if ( needPagination ) {
2014-10-12 19:07:47 +02:00
2014-11-03 17:32:25 +01:00
state . pagesCount = Math . ceil ( ( TAB _MIN _WIDTH * tabsCtrl . count ( ) ) / tabsWidth ) ;
state . itemsPerPage = Math . max ( 1 , Math . floor ( tabsCtrl . count ( ) / state . pagesCount ) ) ;
state . tabWidth = tabsWidth / state . itemsPerPage ;
tabsParent . css ( 'width' , state . tabWidth * tabsCtrl . count ( ) + 'px' ) ;
tabs . css ( 'width' , state . tabWidth + 'px' ) ;
2014-10-12 19:07:47 +02:00
2014-11-03 17:32:25 +01:00
var selectedTabPage = getPageForTab ( tabsCtrl . selected ( ) ) ;
setPage ( selectedTabPage ) ;
2014-10-12 19:07:47 +02:00
2014-11-03 17:32:25 +01:00
} else {
2014-10-12 19:07:47 +02:00
2014-11-03 17:32:25 +01:00
if ( paginationToggled ) {
$timeout ( function ( ) {
tabsParent . css ( 'width' , '' ) ;
tabs . css ( 'width' , '' ) ;
slideTabButtons ( 0 ) ;
state . page = - 1 ;
2014-10-12 19:07:47 +02:00
} ) ;
2014-11-03 17:32:25 +01:00
}
2014-10-12 19:07:47 +02:00
2014-11-03 17:32:25 +01:00
}
}
2014-10-12 19:07:47 +02:00
2014-11-03 17:32:25 +01:00
function slideTabButtons ( x ) {
if ( tabsCtrl . pagingOffset === x ) {
// Resolve instantly if no change
return $$q . when ( ) ;
}
2014-10-12 19:07:47 +02:00
2014-11-03 17:32:25 +01:00
var deferred = $$q . defer ( ) ;
2014-10-12 19:07:47 +02:00
2014-11-03 17:32:25 +01:00
tabsCtrl . $$pagingOffset = x ;
tabsParent . css ( $mdEffects . TRANSFORM , 'translate3d(' + x + 'px,0,0)' ) ;
tabsParent . on ( $mdEffects . TRANSITIONEND _EVENT , onTabsParentTransitionEnd ) ;
2014-10-12 19:07:47 +02:00
2014-11-03 17:32:25 +01:00
return deferred . promise ;
2014-10-12 19:07:47 +02:00
2014-11-03 17:32:25 +01:00
function onTabsParentTransitionEnd ( ev ) {
// Make sure this event didn't bubble up from an animation in a child element.
if ( ev . target === tabsParent [ 0 ] ) {
tabsParent . off ( $mdEffects . TRANSITIONEND _EVENT , onTabsParentTransitionEnd ) ;
deferred . resolve ( ) ;
}
}
}
2014-10-12 19:07:47 +02:00
2014-11-03 17:32:25 +01:00
function getPageForTab ( tab ) {
var tabIndex = tabsCtrl . indexOf ( tab ) ;
if ( tabIndex === - 1 ) return 0 ;
2014-09-30 12:29:53 +02:00
2014-11-03 17:32:25 +01:00
return Math . floor ( tabIndex / state . itemsPerPage ) ;
}
2014-09-30 12:29:53 +02:00
2014-11-03 17:32:25 +01:00
function setPage ( page ) {
if ( page === state . page ) return ;
2014-09-30 12:29:53 +02:00
2014-11-03 17:32:25 +01:00
var lastPage = state . pagesCount ;
2014-10-12 19:07:47 +02:00
2014-11-03 17:32:25 +01:00
if ( page < 0 ) page = 0 ;
if ( page > lastPage ) page = lastPage ;
2014-10-12 19:07:47 +02:00
2014-11-03 17:32:25 +01:00
state . hasPrev = page > 0 ;
state . hasNext = ( ( page + 1 ) * state . itemsPerPage ) < tabsCtrl . count ( ) ;
2014-10-12 19:07:47 +02:00
2014-11-03 17:32:25 +01:00
state . page = page ;
2014-10-12 19:07:47 +02:00
2014-11-03 17:32:25 +01:00
$timeout ( function ( ) {
scope . $broadcast ( '$mdTabsPaginationChanged' ) ;
} ) ;
return slideTabButtons ( - page * state . itemsPerPage * state . tabWidth ) ;
2014-09-30 12:29:53 +02:00
}
2014-11-03 17:32:25 +01:00
}
2014-09-30 12:29:53 +02:00
2014-11-03 17:32:25 +01:00
}
2014-10-12 19:07:47 +02:00
} ) ( ) ;
2014-11-03 17:32:25 +01:00
( function ( ) {
2014-09-30 12:29:53 +02:00
2014-11-03 17:32:25 +01:00
angular . module ( 'material.components.tabs' )
2014-09-30 12:29:53 +02:00
2014-11-03 17:32:25 +01:00
. controller ( '$mdTab' , [
'$scope' ,
'$element' ,
'$compile' ,
'$animate' ,
'$mdSwipe' ,
'$mdUtil' ,
TabItemController
] ) ;
2014-09-30 12:29:53 +02:00
2014-11-03 17:32:25 +01:00
function TabItemController ( scope , element , $compile , $animate , $mdSwipe , $mdUtil ) {
var self = this ;
2014-09-30 12:29:53 +02:00
2014-11-03 17:32:25 +01:00
var detachSwipe = angular . noop ;
var attachSwipe = function ( ) { return detachSwipe ; } ;
var eventTypes = "swipeleft swiperight" ;
var configureSwipe = $mdSwipe ( scope , eventTypes ) ;
2014-09-30 12:29:53 +02:00
2014-11-03 17:32:25 +01:00
// special callback assigned by TabsController
self . $$onSwipe = angular . noop ;
2014-09-30 12:29:53 +02:00
2014-11-03 17:32:25 +01:00
// Properties
2014-11-06 16:55:16 +01:00
self . contentContainer = angular . element ( '<div class="md-tab-content ng-hide">' ) ;
2014-11-03 17:32:25 +01:00
self . element = element ;
// Methods
self . isDisabled = isDisabled ;
self . onAdd = onAdd ;
self . onRemove = onRemove ;
self . onSelect = onSelect ;
self . onDeselect = onDeselect ;
function isDisabled ( ) {
return element [ 0 ] . hasAttribute ( 'disabled' ) ;
2014-09-30 12:29:53 +02:00
}
2014-11-03 17:32:25 +01:00
/ * *
* Add the tab ' s content to the DOM container area in the tabs ,
* @ param contentArea the contentArea to add the content of the tab to
* /
function onAdd ( contentArea ) {
if ( self . content . length ) {
2014-09-30 12:29:53 +02:00
2014-11-03 17:32:25 +01:00
self . contentContainer . append ( self . content ) ;
self . contentScope = scope . $parent . $new ( ) ;
contentArea . append ( self . contentContainer ) ;
2014-09-30 12:29:53 +02:00
2014-11-03 17:32:25 +01:00
$compile ( self . contentContainer ) ( self . contentScope ) ;
2014-09-30 12:29:53 +02:00
2014-11-03 17:32:25 +01:00
$mdUtil . disconnectScope ( self . contentScope ) ;
// For internal tab views we only use the `$mdSwipe`
// so we can easily attach()/detach() when the tab view is active/inactive
attachSwipe = configureSwipe ( self . contentContainer , function ( ev ) {
self . $$onSwipe ( ev . type ) ;
} , true ) ;
}
}
2014-09-30 12:29:53 +02:00
/ * *
2014-11-03 17:32:25 +01:00
* Usually called when a Tab is programmatically removed ; such
* as in an ng - repeat
2014-09-30 12:29:53 +02:00
* /
2014-11-03 17:32:25 +01:00
function onRemove ( ) {
$animate . leave ( self . contentContainer ) . then ( function ( ) {
self . contentScope && self . contentScope . $destroy ( ) ;
self . contentScope = null ;
} ) ;
}
2014-09-30 12:29:53 +02:00
2014-11-03 17:32:25 +01:00
function onSelect ( ) {
// Resume watchers and events firing when tab is selected
$mdUtil . reconnectScope ( self . contentScope ) ;
detachSwipe = attachSwipe ( ) ;
2014-09-30 12:29:53 +02:00
2014-11-03 17:32:25 +01:00
element . addClass ( 'active' ) ;
element . attr ( 'aria-selected' , true ) ;
element . attr ( 'tabIndex' , 0 ) ;
$animate . removeClass ( self . contentContainer , 'ng-hide' ) ;
scope . onSelect ( ) ;
2014-09-30 12:29:53 +02:00
}
2014-11-03 17:32:25 +01:00
function onDeselect ( ) {
// Stop watchers & events from firing while tab is deselected
$mdUtil . disconnectScope ( self . contentScope ) ;
detachSwipe ( ) ;
2014-09-30 12:29:53 +02:00
2014-11-03 17:32:25 +01:00
element . removeClass ( 'active' ) ;
element . attr ( 'aria-selected' , false ) ;
// Only allow tabbing to the active tab
element . attr ( 'tabIndex' , - 1 ) ;
$animate . addClass ( self . contentContainer , 'ng-hide' ) ;
scope . onDeselect ( ) ;
2014-09-30 12:29:53 +02:00
}
2014-11-03 17:32:25 +01:00
2014-09-30 12:29:53 +02:00
}
2014-11-03 17:32:25 +01:00
} ) ( ) ;
( function ( ) {
angular . module ( 'material.components.tabs' )
. directive ( 'mdTab' , [
'$mdInkRipple' ,
'$compile' ,
'$mdAria' ,
'$mdUtil' ,
'$mdConstant' ,
MdTabDirective
] ) ;
2014-09-30 12:29:53 +02:00
/ * *
2014-11-03 17:32:25 +01:00
* @ ngdoc directive
* @ name mdTab
* @ module material . components . tabs
*
* @ restrict E
*
* @ description
* ` <md-tab> ` is the nested directive used [ within ` <md-tabs> ` ] to specify each tab with a * * label * * and optional * view content * .
*
* If the ` label ` attribute is not specified , then an optional ` <md-tab-label> ` tag can be used to specified more
* complex tab header markup . If neither the * * label * * nor the * * md - tab - label * * are specified , then the nested
* markup of the ` <md-tab> ` is used as the tab header markup .
*
* If a tab * * label * * has been identified , then any * * non - * * ` <md-tab-label> ` markup
2014-11-06 16:55:16 +01:00
* will be considered tab content and will be transcluded to the internal ` <div class="md-tabs-content"> ` container .
2014-11-03 17:32:25 +01:00
*
* This container is used by the TabsController to show / hide the active tab ' s content view . This synchronization is
* automatically managed by the internal TabsController whenever the tab selection changes . Selection changes can
* be initiated via data binding changes , programmatic invocation , or user gestures .
*
* @ param { string = } label Optional attribute to specify a simple string as the tab label
* @ param { boolean = } active When evaluteing to true , selects the tab .
* @ param { boolean = } disabled If present , disabled tab selection .
* @ param { expression = } deselected Expression to be evaluated after the tab has been de - selected .
* @ param { expression = } selected Expression to be evaluated after the tab has been selected .
*
*
* @ usage
*
* < hljs lang = "html" >
* < md - tab label = "" disabled = "" selected = "" deselected = "" >
* < h3 > My Tab content < / h 3 >
* < / m d - t a b >
*
* < md - tab >
* < md - tab - label >
* < h3 > My Tab content < / h 3 >
* < / m d - t a b - l a b e l >
* < p >
* Sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium doloremque laudantium ,
* totam rem aperiam , eaque ipsa quae ab illo inventore veritatis et quasi architecto beatae vitae
* dicta sunt explicabo . Nemo enim ipsam voluptatem quia voluptas sit aspernatur aut odit aut fugit ,
* sed quia consequuntur magni dolores eos qui ratione voluptatem sequi nesciunt .
* < / p >
* < / m d - t a b >
* < / h l j s >
2014-09-30 12:29:53 +02:00
*
* /
2014-11-03 17:32:25 +01:00
function MdTabDirective ( $mdInkRipple , $compile , $mdAria , $mdUtil , $mdConstant ) {
return {
restrict : 'E' ,
require : [ 'mdTab' , '^mdTabs' ] ,
controller : '$mdTab' ,
scope : {
onSelect : '&' ,
onDeselect : '&' ,
label : '@'
} ,
compile : compile
} ;
2014-09-30 12:29:53 +02:00
2014-11-03 17:32:25 +01:00
function compile ( element , attr ) {
var tabLabel = element . find ( 'md-tab-label' ) ;
2014-09-30 12:29:53 +02:00
2014-11-03 17:32:25 +01:00
if ( tabLabel . length ) {
2014-09-30 12:29:53 +02:00
2014-11-03 17:32:25 +01:00
// If a tab label element is found, remove it for later re-use.
tabLabel . remove ( ) ;
2014-09-30 12:29:53 +02:00
2014-11-03 17:32:25 +01:00
} else if ( angular . isDefined ( attr . label ) ) {
2014-09-30 12:29:53 +02:00
2014-11-03 17:32:25 +01:00
// Otherwise, try to use attr.label as the label
tabLabel = angular . element ( '<md-tab-label>' ) . html ( attr . label ) ;
2014-09-30 12:29:53 +02:00
2014-11-03 17:32:25 +01:00
} else {
2014-09-30 12:29:53 +02:00
2014-11-03 17:32:25 +01:00
// If nothing is found, use the tab's content as the label
tabLabel = angular . element ( '<md-tab-label>' )
. append ( element . contents ( ) . remove ( ) ) ;
}
2014-09-30 12:29:53 +02:00
2014-11-03 17:32:25 +01:00
// Everything that's left as a child is the tab's content.
var tabContent = element . contents ( ) . remove ( ) ;
2014-09-30 12:29:53 +02:00
2014-11-03 17:32:25 +01:00
return function postLink ( scope , element , attr , ctrls ) {
2014-09-30 12:29:53 +02:00
2014-11-03 17:32:25 +01:00
var tabItemCtrl = ctrls [ 0 ] ; // Controller for THIS tabItemCtrl
var tabsCtrl = ctrls [ 1 ] ; // Controller for ALL tabs
2014-09-30 12:29:53 +02:00
2014-11-03 17:32:25 +01:00
transcludeTabContent ( ) ;
2014-09-30 12:29:53 +02:00
2014-11-03 17:32:25 +01:00
var detachRippleFn = $mdInkRipple . attachButtonBehavior ( element ) ;
tabsCtrl . add ( tabItemCtrl ) ;
scope . $on ( '$destroy' , function ( ) {
detachRippleFn ( ) ;
tabsCtrl . remove ( tabItemCtrl ) ;
} ) ;
2014-09-30 12:29:53 +02:00
2014-11-03 17:32:25 +01:00
if ( ! angular . isDefined ( attr . ngClick ) ) element . on ( 'click' , defaultClickListener ) ;
element . on ( 'keydown' , keydownListener ) ;
2014-09-30 12:29:53 +02:00
2014-11-03 17:32:25 +01:00
if ( angular . isNumber ( scope . $parent . $index ) ) watchNgRepeatIndex ( ) ;
if ( angular . isDefined ( attr . active ) ) watchActiveAttribute ( ) ;
watchDisabled ( ) ;
configureAria ( ) ;
function transcludeTabContent ( ) {
// Clone the label we found earlier, and $compile and append it
var label = tabLabel . clone ( ) ;
element . append ( label ) ;
$compile ( label ) ( scope . $parent ) ;
// Clone the content we found earlier, and mark it for later placement into
// the proper content area.
tabItemCtrl . content = tabContent . clone ( ) ;
2014-09-30 12:29:53 +02:00
}
2014-11-03 17:32:25 +01:00
//defaultClickListener isn't applied if the user provides an ngClick expression.
function defaultClickListener ( ) {
scope . $apply ( function ( ) {
tabsCtrl . select ( tabItemCtrl ) ;
tabItemCtrl . element . focus ( ) ;
} ) ;
}
function keydownListener ( ev ) {
if ( ev . which == $mdConstant . KEY _CODE . SPACE ) {
// Fire the click handler to do normal selection if space is pressed
element . triggerHandler ( 'click' ) ;
ev . preventDefault ( ) ;
2014-09-30 12:29:53 +02:00
2014-11-03 17:32:25 +01:00
} else if ( ev . which === $mdConstant . KEY _CODE . LEFT _ARROW ) {
var previous = tabsCtrl . previous ( tabItemCtrl ) ;
previous && previous . element . focus ( ) ;
2014-09-30 12:29:53 +02:00
2014-11-03 17:32:25 +01:00
} else if ( ev . which === $mdConstant . KEY _CODE . RIGHT _ARROW ) {
var next = tabsCtrl . next ( tabItemCtrl ) ;
next && next . element . focus ( ) ;
}
}
2014-09-30 12:29:53 +02:00
2014-11-03 17:32:25 +01:00
// If tabItemCtrl is part of an ngRepeat, move the tabItemCtrl in our internal array
// when its $index changes
function watchNgRepeatIndex ( ) {
// The tabItemCtrl has an isolate scope, so we watch the $index on the parent.
scope . $watch ( '$parent.$index' , function $indexWatchAction ( newIndex ) {
tabsCtrl . move ( tabItemCtrl , newIndex ) ;
} ) ;
}
2014-09-30 12:29:53 +02:00
2014-11-03 17:32:25 +01:00
function watchActiveAttribute ( ) {
var unwatch = scope . $parent . $watch ( '!!(' + attr . active + ')' , activeWatchAction ) ;
scope . $on ( '$destroy' , unwatch ) ;
function activeWatchAction ( isActive ) {
var isSelected = tabsCtrl . selected ( ) === tabItemCtrl ;
2014-09-30 12:29:53 +02:00
2014-11-03 17:32:25 +01:00
if ( isActive && ! isSelected ) {
tabsCtrl . select ( tabItemCtrl ) ;
} else if ( ! isActive && isSelected ) {
tabsCtrl . deselect ( tabItemCtrl ) ;
}
}
2014-09-30 12:29:53 +02:00
}
2014-11-03 17:32:25 +01:00
function watchDisabled ( ) {
scope . $watch ( tabItemCtrl . isDisabled , disabledWatchAction ) ;
function disabledWatchAction ( isDisabled ) {
element . attr ( 'aria-disabled' , isDisabled ) ;
2014-09-30 12:29:53 +02:00
2014-11-03 17:32:25 +01:00
// Auto select `next` tab when disabled
var isSelected = ( tabsCtrl . selected ( ) === tabItemCtrl ) ;
if ( isSelected && isDisabled ) {
tabsCtrl . select ( tabsCtrl . next ( ) || tabsCtrl . previous ( ) ) ;
}
2014-09-30 12:29:53 +02:00
2014-11-03 17:32:25 +01:00
}
}
2014-09-30 12:29:53 +02:00
2014-11-03 17:32:25 +01:00
function configureAria ( ) {
// Link together the content area and tabItemCtrl with an id
var tabId = attr . id || $mdUtil . nextUid ( ) ;
var tabContentId = 'content_' + tabId ;
element . attr ( {
id : tabId ,
role : 'tabItemCtrl' ,
tabIndex : '-1' , //this is also set on select/deselect in tabItemCtrl
'aria-controls' : tabContentId
} ) ;
tabItemCtrl . contentContainer . attr ( {
id : tabContentId ,
role : 'tabpanel' ,
'aria-labelledby' : tabId
} ) ;
2014-09-30 12:29:53 +02:00
2014-11-06 16:55:16 +01:00
$mdAria . expect ( element , 'aria-label' , true ) ;
2014-11-03 17:32:25 +01:00
}
2014-09-30 12:29:53 +02:00
2014-11-03 17:32:25 +01:00
} ;
}
2014-09-30 12:29:53 +02:00
}
2014-11-03 17:32:25 +01:00
} ) ( ) ;
2014-10-12 19:07:47 +02:00
2014-11-03 17:32:25 +01:00
( function ( ) {
angular . module ( 'material.components.tabs' )
. controller ( '$mdTabs' , [
'$scope' ,
'$element' ,
'$mdUtil' ,
MdTabsController
2014-10-12 19:07:47 +02:00
] ) ;
2014-11-03 17:32:25 +01:00
function MdTabsController ( scope , element , $mdUtil ) {
2014-10-12 19:07:47 +02:00
2014-11-03 17:32:25 +01:00
var tabsList = $mdUtil . iterator ( [ ] , false ) ;
var self = this ;
2014-10-12 19:07:47 +02:00
2014-11-03 17:32:25 +01:00
// Properties
self . element = element ;
// The section containing the tab content elements
2014-11-06 16:55:16 +01:00
self . contentArea = angular . element ( element [ 0 ] . querySelector ( '.md-tabs-content' ) ) ;
2014-10-12 19:07:47 +02:00
2014-11-03 17:32:25 +01:00
// Methods from iterator
self . inRange = tabsList . inRange ;
self . indexOf = tabsList . indexOf ;
self . itemAt = tabsList . itemAt ;
self . count = tabsList . count ;
self . selected = selected ;
self . add = add ;
self . remove = remove ;
self . move = move ;
self . select = select ;
self . deselect = deselect ;
2014-10-12 19:07:47 +02:00
2014-11-03 17:32:25 +01:00
self . next = next ;
self . previous = previous ;
2014-10-12 19:07:47 +02:00
2014-11-03 17:32:25 +01:00
self . swipe = swipe ;
2014-10-12 19:07:47 +02:00
2014-11-03 17:32:25 +01:00
scope . $on ( '$destroy' , function ( ) {
self . deselect ( self . selected ( ) ) ;
for ( var i = tabsList . count ( ) - 1 ; i >= 0 ; i -- ) {
self . remove ( tabsList [ i ] , true ) ;
}
} ) ;
2014-10-12 19:07:47 +02:00
2014-11-03 17:32:25 +01:00
// Get the selected tab
function selected ( ) {
return self . itemAt ( scope . selectedIndex ) ;
}
2014-10-12 19:07:47 +02:00
2014-11-03 17:32:25 +01:00
// Add a new tab.
// Returns a method to remove the tab from the list.
function add ( tab , index ) {
2014-10-12 19:07:47 +02:00
2014-11-03 17:32:25 +01:00
tabsList . add ( tab , index ) ;
tab . onAdd ( self . contentArea ) ;
2014-10-12 19:07:47 +02:00
2014-11-03 17:32:25 +01:00
// Register swipe feature
tab . $$onSwipe = swipe ;
2014-10-12 19:07:47 +02:00
2014-11-03 17:32:25 +01:00
// Select the new tab if we don't have a selectedIndex, or if the
// selectedIndex we've been waiting for is this tab
if ( scope . selectedIndex === - 1 || scope . selectedIndex === self . indexOf ( tab ) ) {
self . select ( tab ) ;
2014-10-12 19:07:47 +02:00
}
2014-11-03 17:32:25 +01:00
scope . $broadcast ( '$mdTabsChanged' ) ;
}
2014-10-12 19:07:47 +02:00
2014-11-03 17:32:25 +01:00
function remove ( tab , noReselect ) {
if ( ! tabsList . contains ( tab ) ) return ;
if ( noReselect ) {
} else if ( self . selected ( ) === tab ) {
if ( tabsList . count ( ) > 1 ) {
self . select ( self . previous ( ) || self . next ( ) ) ;
} else {
self . deselect ( tab ) ;
}
2014-10-12 19:07:47 +02:00
}
2014-11-03 17:32:25 +01:00
tabsList . remove ( tab ) ;
tab . onRemove ( ) ;
scope . $broadcast ( '$mdTabsChanged' ) ;
}
// Move a tab (used when ng-repeat order changes)
function move ( tab , toIndex ) {
var isSelected = self . selected ( ) === tab ;
tabsList . remove ( tab ) ;
tabsList . add ( tab , toIndex ) ;
if ( isSelected ) self . select ( tab ) ;
scope . $broadcast ( '$mdTabsChanged' ) ;
}
function select ( tab ) {
if ( ! tab || tab . isSelected || tab . isDisabled ( ) ) return ;
if ( ! tabsList . contains ( tab ) ) return ;
self . deselect ( self . selected ( ) ) ;
2014-10-12 19:07:47 +02:00
2014-11-03 17:32:25 +01:00
scope . selectedIndex = self . indexOf ( tab ) ;
tab . isSelected = true ;
tab . onSelect ( ) ;
}
function deselect ( tab ) {
if ( ! tab || ! tab . isSelected ) return ;
if ( ! tabsList . contains ( tab ) ) return ;
2014-10-12 19:07:47 +02:00
2014-11-03 17:32:25 +01:00
scope . selectedIndex = - 1 ;
tab . isSelected = false ;
tab . onDeselect ( ) ;
}
2014-10-12 19:07:47 +02:00
2014-11-03 17:32:25 +01:00
function next ( tab , filterFn ) {
return tabsList . next ( tab || self . selected ( ) , filterFn || isTabEnabled ) ;
}
function previous ( tab , filterFn ) {
return tabsList . previous ( tab || self . selected ( ) , filterFn || isTabEnabled ) ;
}
2014-10-12 19:07:47 +02:00
2014-11-03 17:32:25 +01:00
function isTabEnabled ( tab ) {
return tab && ! tab . isDisabled ( ) ;
}
2014-10-12 19:07:47 +02:00
2014-11-03 17:32:25 +01:00
/ *
* attach a swipe listen
* if it ' s not selected , abort
* check the direction
* if it is right
* it pan right
* Now select
* /
function swipe ( direction ) {
if ( ! self . selected ( ) ) return ;
// check the direction
switch ( direction ) {
case "swiperight" : // if it is right
case "panright" : // it pan right
// Now do this...
self . select ( self . previous ( ) ) ;
break ;
case "swipeleft" :
case "panleft" :
self . select ( self . next ( ) ) ;
break ;
2014-10-12 19:07:47 +02:00
}
2014-11-03 17:32:25 +01:00
}
2014-10-12 19:07:47 +02:00
}
2014-11-03 17:32:25 +01:00
} ) ( ) ;
2014-10-12 19:07:47 +02:00
2014-11-03 17:32:25 +01:00
( function ( ) {
angular . module ( 'material.components.tabs' )
2014-10-12 19:07:47 +02:00
2014-09-30 12:29:53 +02:00
/ * *
2014-11-03 17:32:25 +01:00
* @ ngdoc directive
* @ name mdTabs
* @ module material . components . tabs
2014-09-30 12:29:53 +02:00
*
2014-11-03 17:32:25 +01:00
* @ restrict E
2014-09-30 12:29:53 +02:00
*
* @ description
2014-11-03 17:32:25 +01:00
* The ` <md-tabs> ` directive serves as the container for 1. . n ` <md-tab> ` child directives to produces a Tabs components .
* In turn , the nested ` <md-tab> ` directive is used to specify a tab label for the * * header button * * and a [ optional ] tab view
* content that will be associated with each tab button .
*
* Below is the markup for its simplest usage :
*
* < hljs lang = "html" >
* < md - tabs >
* < md - tab label = "Tab #1" > < / m d - t a b >
* < md - tab label = "Tab #2" > < / m d - t a b >
* < md - tab label = "Tab #3" > < / m d - t a b >
* < md - tabs >
* < / h l j s >
*
* Tabs supports three ( 3 ) usage scenarios :
*
* 1. Tabs ( buttons only )
* 2. Tabs with internal view content
* 3. Tabs with external view content
*
* * * Tab - only * * support is useful when tab buttons are used for custom navigation regardless of any other components , content , or views .
* * * Tabs with internal views * * are the traditional usages where each tab has associated view content and the view switching is managed internally by the Tabs component .
* * * Tabs with external view content * * is often useful when content associated with each tab is independently managed and data - binding notifications announce tab selection changes .
*
* > As a performance bonus , if the tab content is managed internally then the non - active ( non - visible ) tab contents are temporarily disconnected from the ` $ scope. $ digest() ` processes ; which restricts and optimizes DOM updates to only the currently active tab .
*
* Additional features also include :
*
* * Content can include any markup .
* * If a tab is disabled while active / selected , then the next tab will be auto - selected .
* * If the currently active tab is the last tab , then next ( ) action will select the first tab .
* * Any markup ( other than * * ` <md-tab> ` * * tags ) will be transcluded into the tab header area BEFORE the tab buttons .
*
* @ param { integer = } selected Index of the active / selected tab
* @ param { boolean = } noink If present , disables ink ripple effects .
* @ param { boolean = } nobar If present , disables the selection ink bar .
* @ param { string = } align - tabs Attribute to indicate position of tab buttons : bottom or top ; default is ` top `
*
* @ usage
* < hljs lang = "html" >
* < md - tabs selected = "selectedIndex" >
* < img ng - src = "/img/angular.png" class = "centered" >
*
* < md - tab
* ng - repeat = "tab in tabs | orderBy:predicate:reversed"
* on - select = "onTabSelected(tab)"
* on - deselect = "announceDeselected(tab)"
* disabled = "tab.disabled" >
*
* < md - tab - label >
* { { tab . title } }
* < img src = "/img/removeTab.png"
* ng - click = "removeTab(tab)"
* class = "delete" >
* < / m d - t a b - l a b e l >
*
* { { tab . content } }
*
* < / m d - t a b >
*
* < / m d - t a b s >
* < / h l j s >
*
2014-09-30 12:29:53 +02:00
* /
2014-11-03 17:32:25 +01:00
. directive ( 'mdTabs' , [
'$parse' ,
2014-11-06 16:55:16 +01:00
'$mdTheming' ,
2014-11-03 17:32:25 +01:00
TabsDirective
] ) ;
2014-09-30 12:29:53 +02:00
2014-11-06 16:55:16 +01:00
function TabsDirective ( $parse , $mdTheming ) {
2014-09-30 12:29:53 +02:00
return {
2014-11-03 17:32:25 +01:00
restrict : 'E' ,
controller : '$mdTabs' ,
require : 'mdTabs' ,
transclude : true ,
scope : {
selectedIndex : '=?selected'
2014-09-30 12:29:53 +02:00
} ,
2014-11-06 16:55:16 +01:00
template :
'<section class="md-header" ' +
'ng-class="{\'md-paginating\': pagination.active}">' +
2014-09-30 12:29:53 +02:00
2014-11-06 16:55:16 +01:00
'<div class="md-paginator md-prev" ' +
2014-11-03 17:32:25 +01:00
'ng-if="pagination.active && pagination.hasPrev" ' +
'ng-click="pagination.clickPrevious()">' +
'</div>' +
2014-09-30 12:29:53 +02:00
2014-11-03 17:32:25 +01:00
// overflow: hidden container when paginating
2014-11-06 16:55:16 +01:00
'<div class="md-header-items-container" md-tabs-pagination>' +
2014-11-03 17:32:25 +01:00
// flex container for <md-tab> elements
2014-11-06 16:55:16 +01:00
'<div class="md-header-items" ng-transclude></div>' +
2014-11-03 17:32:25 +01:00
'<md-tabs-ink-bar></md-tabs-ink-bar>' +
'</div>' +
2014-11-06 16:55:16 +01:00
'<div class="md-paginator md-next" ' +
2014-11-03 17:32:25 +01:00
'ng-if="pagination.active && pagination.hasNext" ' +
'ng-click="pagination.clickNext()">' +
'</div>' +
'</section>' +
2014-11-06 16:55:16 +01:00
'<section class="md-tabs-content"></section>' ,
2014-11-03 17:32:25 +01:00
link : postLink
} ;
function postLink ( scope , element , attr , tabsCtrl ) {
2014-11-06 16:55:16 +01:00
$mdTheming ( element ) ;
2014-11-03 17:32:25 +01:00
configureAria ( ) ;
watchSelected ( ) ;
function configureAria ( ) {
element . attr ( {
role : 'tablist'
} ) ;
}
function watchSelected ( ) {
scope . $watch ( 'selectedIndex' , function watchSelectedIndex ( newIndex , oldIndex ) {
// Note: if the user provides an invalid newIndex, all tabs will be deselected
// and the associated view will be hidden.
tabsCtrl . deselect ( tabsCtrl . itemAt ( oldIndex ) ) ;
if ( tabsCtrl . inRange ( newIndex ) ) {
var newTab = tabsCtrl . itemAt ( newIndex ) ;
// If the newTab is disabled, find an enabled one to go to.
if ( newTab && newTab . isDisabled ( ) ) {
newTab = newIndex > oldIndex ?
tabsCtrl . next ( newTab ) :
tabsCtrl . previous ( newTab ) ;
}
tabsCtrl . select ( newTab ) ;
2014-09-30 12:29:53 +02:00
}
2014-11-03 17:32:25 +01:00
} ) ;
2014-09-30 12:29:53 +02:00
}
2014-11-03 17:32:25 +01:00
2014-09-30 12:29:53 +02:00
}
}
2014-11-03 17:32:25 +01:00
} ) ( ) ;