text: /*
   * This var has to be outside the angular factory, otherwise when
   * there are multiple material apps on the same page, each app
   * will create its own instance of this array and the app's IDs
   * will not be unique.
   */
  var nextUniqueId = 0;
  
  /**
   * @ngdoc module
   * @name material.core.util
   * @description
   * Util
   */
  angular
    .module('material.core')
    .factory('$mdUtil', UtilFactory);
  
  /**
   * @ngInject
   */
  function UtilFactory($document, $timeout, $compile, $rootScope, $$mdAnimate, $interpolate, $log, $rootElement, $window) {
    // Setup some core variables for the processTemplate method
    var startSymbol = $interpolate.startSymbol(),
      endSymbol = $interpolate.endSymbol(),
      usesStandardSymbols = ((startSymbol === '{{') && (endSymbol === '}}'));
  
    /**
     * Checks if the target element has the requested style by key
     * @param {DOMElement|JQLite} target Target element
     * @param {string} key Style key
     * @param {string=} expectedVal Optional expected value
     * @returns {boolean} Whether the target element has the style or not
     */
    var hasComputedStyle = function (target, key, expectedVal) {
      var hasValue = false;
  
      if ( target && target.length  ) {
        var computedStyles = $window.getComputedStyle(target[0]);
        hasValue = angular.isDefined(computedStyles[key]) && (expectedVal ? computedStyles[key] == expectedVal : true);
      }
  
      return hasValue;
    };
  
    var $mdUtil = {
      dom: {},
      now: window.performance ?
        angular.bind(window.performance, window.performance.now) : Date.now || function() {
        return new Date().getTime();
      },
  
      /**
       * Bi-directional accessor/mutator used to easily update an element's
       * property based on the current 'dir'ectional value.
       */
      bidi : function(element, property, lValue, rValue) {
        var ltr = !($document[0].dir == 'rtl' || $document[0].body.dir == 'rtl');
  
        // If accessor
        if ( arguments.length == 0 ) return ltr ? 'ltr' : 'rtl';
  
        // If mutator
        if ( ltr && angular.isDefined(lValue)) {
          angular.element(element).css(property, validate(lValue));
        }
        else if ( !ltr && angular.isDefined(rValue)) {
          angular.element(element).css(property, validate(rValue) );
        }
  
          // Internal utils
  
          function validate(value) {
            return !value       ? '0'   :
                   hasPx(value) ? value : value + 'px';
          }
          function hasPx(value) {
            return String(value).indexOf('px') > -1;
          }
      },
  
      clientRect: function(element, offsetParent, isOffsetRect) {
        var node = getNode(element);
        offsetParent = getNode(offsetParent || node.offsetParent || document.body);
        var nodeRect = node.getBoundingClientRect();
  
        // The user can ask for an offsetRect: a rect relative to the offsetParent,
        // or a clientRect: a rect relative to the page
        var offsetRect = isOffsetRect ?
          offsetParent.getBoundingClientRect() :
        {left: 0, top: 0, width: 0, height: 0};
        return {
          left: nodeRect.left - offsetRect.left,
          top: nodeRect.top - offsetRect.top,
          width: nodeRect.width,
          height: nodeRect.height
        };
      },
      offsetRect: function(element, offsetParent) {
        return $mdUtil.clientRect(element, offsetParent, true);
      },
  
      // Annoying method to copy nodes to an array, thanks to IE
      nodesToArray: function(nodes) {
        nodes = nodes || [];
  
        var results = [];
        for (var i = 0; i < nodes.length; ++i) {
          results.push(nodes.item(i));
        }
        return results;
      },
  
      /**
       * Calculate the positive scroll offset
       * TODO: Check with pinch-zoom in IE/Chrome;
       *       https://code.google.com/p/chromium/issues/detail?id=496285
       */
      scrollTop: function(element) {
        element = angular.element(element || $document[0].body);
  
        var body = (element[0] == $document[0].body) ? $document[0].body : undefined;
        var scrollTop = body ? body.scrollTop + body.parentElement.scrollTop : 0;
  
        // Calculate the positive scroll offset
        return scrollTop || Math.abs(element[0].getBoundingClientRect().top);
      },
  
      /**
       * Finds the proper focus target by searching the DOM.
       *
       * @param containerEl
       * @param attributeVal
       * @returns {*}
       */
      findFocusTarget: function(containerEl, attributeVal) {
        var AUTO_FOCUS = '[md-autofocus]';
        var elToFocus;
  
        elToFocus = scanForFocusable(containerEl, attributeVal || AUTO_FOCUS);
  
        if ( !elToFocus && attributeVal != AUTO_FOCUS) {
          // Scan for deprecated attribute
          elToFocus = scanForFocusable(containerEl, '[md-auto-focus]');
  
          if ( !elToFocus ) {
            // Scan for fallback to 'universal' API
            elToFocus = scanForFocusable(containerEl, AUTO_FOCUS);
          }
        }
  
        return elToFocus;
  
        /**
         * Can target and nested children for specified Selector (attribute)
         * whose value may be an expression that evaluates to True/False.
         */
        function scanForFocusable(target, selector) {
          var elFound, items = target[0].querySelectorAll(selector);
  
          // Find the last child element with the focus attribute
          if ( items && items.length ){
            items.length && angular.forEach(items, function(it) {
              it = angular.element(it);
  
              // Check the element for the _md-autofocus class to ensure any associated expression
              // evaluated to true.
              var isFocusable = it.hasClass('_md-autofocus');
              if (isFocusable) elFound = it;
            });
          }
          return elFound;
        }
      },
  
      /**
       * Disables scroll around the passed parent element.
       * @param element Unused
       * @param {!Element|!angular.JQLite} parent Element to disable scrolling within.
       *   Defaults to body if none supplied.
       */
      disableScrollAround: function(element, parent) {
        $mdUtil.disableScrollAround._count = $mdUtil.disableScrollAround._count || 0;
        ++$mdUtil.disableScrollAround._count;
        if ($mdUtil.disableScrollAround._enableScrolling) return $mdUtil.disableScrollAround._enableScrolling;
        var body = $document[0].body,
          restoreBody = disableBodyScroll(),
          restoreElement = disableElementScroll(parent);
  
        return $mdUtil.disableScrollAround._enableScrolling = function() {
          if (!--$mdUtil.disableScrollAround._count) {
            restoreBody();
            restoreElement();
            delete $mdUtil.disableScrollAround._enableScrolling;
          }
        };
  
        // Creates a virtual scrolling mask to absorb touchmove, keyboard, scrollbar clicking, and wheel events
        function disableElementScroll(element) {
          element = angular.element(element || body)[0];
          var zIndex = 50;
          var scrollMask = angular.element(
            '<div class="md-scroll-mask">' +
            '  <div class="md-scroll-mask-bar"></div>' +
            '</div>').css('z-index', zIndex);
          element.appendChild(scrollMask[0]);
  
          scrollMask.on('wheel', preventDefault);
          scrollMask.on('touchmove', preventDefault);
  
          return function restoreScroll() {
            scrollMask.off('wheel');
            scrollMask.off('touchmove');
            scrollMask[0].parentNode.removeChild(scrollMask[0]);
            delete $mdUtil.disableScrollAround._enableScrolling;
          };
  
          function preventDefault(e) {
            e.preventDefault();
          }
        }
  
        // Converts the body to a position fixed block and translate it to the proper scroll
        // position
        function disableBodyScroll() {
          var htmlNode = body.parentNode;
          var restoreHtmlStyle = htmlNode.style.cssText || '';
          var restoreBodyStyle = body.style.cssText || '';
          var scrollOffset = $mdUtil.scrollTop(body);
          var clientWidth = body.clientWidth;
  
          if (body.scrollHeight > body.clientHeight + 1) {
            applyStyles(body, {
              position: 'fixed',
              width: '100%',
              top: -scrollOffset + 'px'
            });
  
            applyStyles(htmlNode, {
              overflowY: 'scroll'
            });
          }
  
          if (body.clientWidth < clientWidth) applyStyles(body, {overflow: 'hidden'});
  
          return function restoreScroll() {
            body.style.cssText = restoreBodyStyle;
            htmlNode.style.cssText = restoreHtmlStyle;
            body.scrollTop = scrollOffset;
            htmlNode.scrollTop = scrollOffset;
          };
        }
  
        function applyStyles(el, styles) {
          for (var key in styles) {
            el.style[key] = styles[key];
          }
        }
      },
      enableScrolling: function() {
        var method = this.disableScrollAround._enableScrolling;
        method && method();
      },
      floatingScrollbars: function() {
        if (this.floatingScrollbars.cached === undefined) {
          var tempNode = angular.element('<div><div></div></div>').css({
            width: '100%',
            'z-index': -1,
            position: 'absolute',
            height: '35px',
            'overflow-y': 'scroll'
          });
          tempNode.children().css('height', '60px');
  
          $document[0].body.appendChild(tempNode[0]);
          this.floatingScrollbars.cached = (tempNode[0].offsetWidth == tempNode[0].childNodes[0].offsetWidth);
          tempNode.remove();
        }
        return this.floatingScrollbars.cached;
      },
  
      // Mobile safari only allows you to set focus in click event listeners...
      forceFocus: function(element) {
        var node = element[0] || element;
  
        document.addEventListener('click', function focusOnClick(ev) {
          if (ev.target === node && ev.$focus) {
            node.focus();
            ev.stopImmediatePropagation();
            ev.preventDefault();
            node.removeEventListener('click', focusOnClick);
          }
        }, true);
  
        var newEvent = document.createEvent('MouseEvents');
        newEvent.initMouseEvent('click', false, true, window, {}, 0, 0, 0, 0,
          false, false, false, false, 0, null);
        newEvent.$material = true;
        newEvent.$focus = true;
        node.dispatchEvent(newEvent);
      },
  
      /**
       * facade to build md-backdrop element with desired styles
       * NOTE: Use $compile to trigger backdrop postLink function
       */
      createBackdrop: function(scope, addClass) {
        return $compile($mdUtil.supplant('<md-backdrop class="{0}">', [addClass]))(scope);
      },
  
      /**
       * supplant() method from Crockford's `Remedial Javascript`
       * Equivalent to use of $interpolate; without dependency on
       * interpolation symbols and scope. Note: the '{<token>}' can
       * be property names, property chains, or array indices.
       */
      supplant: function(template, values, pattern) {
        pattern = pattern || /\{([^\{\}]*)\}/g;
        return template.replace(pattern, function(a, b) {
          var p = b.split('.'),
            r = values;
          try {
            for (var s in p) {
              if (p.hasOwnProperty(s) ) {
                r = r[p[s]];
              }
            }
          } catch (e) {
            r = a;
          }
          return (typeof r === 'string' || typeof r === 'number') ? r : a;
        });
      },
  
      fakeNgModel: function() {
        return {
          $fake: true,
          $setTouched: angular.noop,
          $setViewValue: function(value) {
            this.$viewValue = value;
            this.$render(value);
            this.$viewChangeListeners.forEach(function(cb) {
              cb();
            });
          },
          $isEmpty: function(value) {
            return ('' + value).length === 0;
          },
          $parsers: [],
          $formatters: [],
          $viewChangeListeners: [],
          $render: angular.noop
        };
      },
  
      // 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.
      // @param wait Integer value of msecs to delay (since last debounce reset); default value 10 msecs
      // @param invokeApply should the $timeout trigger $digest() dirty checking
      debounce: function(func, wait, scope, invokeApply) {
        var timer;
  
        return function debounced() {
          var context = scope,
            args = Array.prototype.slice.call(arguments);
  
          $timeout.cancel(timer);
          timer = $timeout(function() {
  
            timer = undefined;
            func.apply(context, args);
  
          }, wait || 10, invokeApply);
        };
      },
  
      // 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 = $mdUtil.now();
  
          if (!recent || (now - recent > delay)) {
            func.apply(context, args);
            recent = now;
          }
        };
      },
  
      /**
       * Measures the number of milliseconds taken to run the provided callback
       * function. Uses a high-precision timer if available.
       */
      time: function time(cb) {
        var start = $mdUtil.now();
        cb();
        return $mdUtil.now() - start;
      },
  
      /**
       * Create an implicit getter that caches its `getter()`
       * lookup value
       */
      valueOnUse : function (scope, key, getter) {
        var value = null, args = Array.prototype.slice.call(arguments);
        var params = (args.length > 3) ? args.slice(3) : [ ];
  
        Object.defineProperty(scope, key, {
          get: function () {
            if (value === null) value = getter.apply(scope, params);
            return value;
          }
        });
      },
  
      /**
       * Get a unique ID.
       *
       * @returns {string} an unique numeric string
       */
      nextUid: function() {
        return '' + nextUniqueId++;
      },
  
      // 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;
  
        // we can't destroy the root scope or a scope that has been already destroyed
        if (scope.$root === scope) return;
        if (scope.$$destroyed) return;
  
        var parent = scope.$parent;
        scope.$$disconnected = true;
  
        // 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;
  
        scope.$$nextSibling = scope.$$prevSibling = null;
  
      },
  
      // Undo the effects of disconnectScope above.
      reconnectScope: function reconnectScope(scope) {
        if (!scope) return;
  
        // we can't disconnect the root node or scope already disconnected
        if (scope.$root === scope) return;
        if (!scope.$$disconnected) return;
  
        var child = scope;
  
        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;
        }
      },
  
      /*
       * getClosest replicates jQuery.closest() to walk up the DOM tree until it finds a matching nodeName
       *
       * @param el Element to start walking the DOM from
       * @param tagName Tag name to find closest to el, such as 'form'
       * @param onlyParent Only start checking from the parent element, not `el`.
       */
      getClosest: function getClosest(el, tagName, onlyParent) {
        if (el instanceof angular.element) el = el[0];
        tagName = tagName.toUpperCase();
        if (onlyParent) el = el.parentNode;
        if (!el) return null;
        do {
          if (el.nodeName === tagName) {
            return el;
          }
        } while (el = el.parentNode);
        return null;
      },
  
      /**
       * Build polyfill for the Node.contains feature (if needed)
       */
      elementContains: function(node, child) {
        var hasContains = (window.Node && window.Node.prototype && Node.prototype.contains);
        var findFn = hasContains ? angular.bind(node, node.contains) : angular.bind(node, function(arg) {
          // compares the positions of two nodes and returns a bitmask
          return (node === child) || !!(this.compareDocumentPosition(arg) & 16)
        });
  
        return findFn(child);
      },
  
      /**
       * Functional equivalent for $element.filter(‘md-bottom-sheet’)
       * useful with interimElements where the element and its container are important...
       *
       * @param {[]} elements to scan
       * @param {string} name of node to find (e.g. 'md-dialog')
       * @param {boolean=} optional flag to allow deep scans; defaults to 'false'.
       * @param {boolean=} optional flag to enable log warnings; defaults to false
       */
      extractElementByName: function(element, nodeName, scanDeep, warnNotFound) {
        var found = scanTree(element);
        if (!found && !!warnNotFound) {
          $log.warn( $mdUtil.supplant("Unable to find node '{0}' in element '{1}'.",[nodeName, element[0].outerHTML]) );
        }
  
        return angular.element(found || element);
  
        /**
         * Breadth-First tree scan for element with matching `nodeName`
         */
        function scanTree(element) {
          return scanLevel(element) || (!!scanDeep ? scanChildren(element) : null);
        }
  
        /**
         * Case-insensitive scan of current elements only (do not descend).
         */
        function scanLevel(element) {
          if ( element ) {
            for (var i = 0, len = element.length; i < len; i++) {
              if (element[i].nodeName.toLowerCase() === nodeName) {
                return element[i];
              }
            }
          }
          return null;
        }
  
        /**
         * Scan children of specified node
         */
        function scanChildren(element) {
          var found;
          if ( element ) {
            for (var i = 0, len = element.length; i < len; i++) {
              var target = element[i];
              if ( !found ) {
                for (var j = 0, numChild = target.childNodes.length; j < numChild; j++) {
                  found = found || scanTree([target.childNodes[j]]);
                }
              }
            }
          }
          return found;
        }
  
      },
  
      /**
       * Give optional properties with no value a boolean true if attr provided or false otherwise
       */
      initOptionalProperties: function(scope, attr, defaults) {
        defaults = defaults || {};
        angular.forEach(scope.$$isolateBindings, function(binding, key) {
          if (binding.optional && angular.isUndefined(scope[key])) {
            var attrIsDefined = angular.isDefined(attr[binding.attrName]);
            scope[key] = angular.isDefined(defaults[key]) ? defaults[key] : attrIsDefined;
          }
        });
      },
  
      /**
       * Alternative to $timeout calls with 0 delay.
       * nextTick() coalesces all calls within a single frame
       * to minimize $digest thrashing
       *
       * @param callback
       * @param digest
       * @returns {*}
       */
      nextTick: function(callback, digest, scope) {
        //-- grab function reference for storing state details
        var nextTick = $mdUtil.nextTick;
        var timeout = nextTick.timeout;
        var queue = nextTick.queue || [];
  
        //-- add callback to the queue
        queue.push(callback);
  
        //-- set default value for digest
        if (digest == null) digest = true;
  
        //-- store updated digest/queue values
        nextTick.digest = nextTick.digest || digest;
        nextTick.queue = queue;
  
        //-- either return existing timeout or create a new one
        return timeout || (nextTick.timeout = $timeout(processQueue, 0, false));
  
        /**
         * Grab a copy of the current queue
         * Clear the queue for future use
         * Process the existing queue
         * Trigger digest if necessary
         */
        function processQueue() {
          var skip = scope && scope.$$destroyed;
          var queue = !skip ? nextTick.queue : [];
          var digest = !skip ? nextTick.digest : null;
  
          nextTick.queue = [];
          nextTick.timeout = null;
          nextTick.digest = false;
  
          queue.forEach(function(callback) {
            callback();
          });
  
          if (digest) $rootScope.$digest();
        }
      },
  
      /**
       * Processes a template and replaces the start/end symbols if the application has
       * overriden them.
       *
       * @param template The template to process whose start/end tags may be replaced.
       * @returns {*}
       */
      processTemplate: function(template) {
        if (usesStandardSymbols) {
          return template;
        } else {
          if (!template || !angular.isString(template)) return template;
          return template.replace(/\{\{/g, startSymbol).replace(/}}/g, endSymbol);
        }
      },
  
      /**
       * Scan up dom hierarchy for enabled parent;
       */
      getParentWithPointerEvents: function (element) {
        var parent = element.parent();
  
        // jqLite might return a non-null, but still empty, parent; so check for parent and length
        while (hasComputedStyle(parent, 'pointer-events', 'none')) {
          parent = parent.parent();
        }
  
        return parent;
      },
  
      getNearestContentElement: function (element) {
        var current = element.parent()[0];
        // Look for the nearest parent md-content, stopping at the rootElement.
        while (current && current !== $rootElement[0] && current !== document.body && current.nodeName.toUpperCase() !== 'MD-CONTENT') {
          current = current.parentNode;
        }
        return current;
      },
  
      /**
       * Parses an attribute value, mostly a string.
       * By default checks for negated values and returns `false´ if present.
       * Negated values are: (native falsy) and negative strings like:
       * `false` or `0`.
       * @param value Attribute value which should be parsed.
       * @param negatedCheck When set to false, won't check for negated values.
       * @returns {boolean}
       */
      parseAttributeBoolean: function(value, negatedCheck) {
        return value === '' || !!value && (negatedCheck === false || value !== 'false' && value !== '0');
      },
  
      hasComputedStyle: hasComputedStyle
    };
  
  // Instantiate other namespace utility methods
  
    $mdUtil.dom.animator = $$mdAnimate($mdUtil);
  
    return $mdUtil;
  
    function getNode(el) {
      return el[0] || el;
    }
  
  }
  
  /*
   * 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`.
   */
  
  angular.element.prototype.focus = angular.element.prototype.focus || function() {
      if (this.length) {
        this[0].focus();
      }
      return this;
    };
  angular.element.prototype.blur = angular.element.prototype.blur || function() {
      if (this.length) {
        this[0].blur();
      }
      return this;
    };
  
  