text: /**
   * @ngdoc module
   * @name material.components.gridList
   */
  angular.module('material.components.gridList', ['material.core'])
         .directive('mdGridList', GridListDirective)
         .directive('mdGridTile', GridTileDirective)
         .directive('mdGridTileFooter', GridTileCaptionDirective)
         .directive('mdGridTileHeader', GridTileCaptionDirective)
         .factory('$mdGridLayout', GridLayoutFactory);
  
  /**
   * @ngdoc directive
   * @name mdGridList
   * @module material.components.gridList
   * @restrict E
   * @description
   * Grid lists are an alternative to standard list views. Grid lists are distinct
   * from grids used for layouts and other visual presentations.
   *
   * A grid list is best suited to presenting a homogenous data type, typically
   * images, and is optimized for visual comprehension and differentiating between
   * like data types.
   *
   * A grid list is a continuous element consisting of tessellated, regular
   * subdivisions called cells that contain tiles (`md-grid-tile`).
   *
   * <img src="//material-design.storage.googleapis.com/publish/v_2/material_ext_publish/0Bx4BSt6jniD7OVlEaXZ5YmU1Xzg/components_grids_usage2.png"
   *    style="width: 300px; height: auto; margin-right: 16px;" alt="Concept of grid explained visually">
   * <img src="//material-design.storage.googleapis.com/publish/v_2/material_ext_publish/0Bx4BSt6jniD7VGhsOE5idWlJWXM/components_grids_usage3.png"
   *    style="width: 300px; height: auto;" alt="Grid concepts legend">
   *
   * Cells are arrayed vertically and horizontally within the grid.
   *
   * Tiles hold content and can span one or more cells vertically or horizontally.
   *
   * ### Responsive Attributes
   *
   * The `md-grid-list` directive supports "responsive" attributes, which allow
   * different `md-cols`, `md-gutter` and `md-row-height` values depending on the
   * currently matching media query.
   *
   * In order to set a responsive attribute, first define the fallback value with
   * the standard attribute name, then add additional attributes with the
   * following convention: `{base-attribute-name}-{media-query-name}="{value}"`
   * (ie. `md-cols-lg="8"`)
   *
   * @param {number} md-cols Number of columns in the grid.
   * @param {string} md-row-height One of
   * <ul>
   *   <li>CSS length - Fixed height rows (eg. `8px` or `1rem`)</li>
   *   <li>`{width}:{height}` - Ratio of width to height (eg.
   *   `md-row-height="16:9"`)</li>
   *   <li>`"fit"` - Height will be determined by subdividing the available
   *   height by the number of rows</li>
   * </ul>
   * @param {string=} md-gutter The amount of space between tiles in CSS units
   *     (default 1px)
   * @param {expression=} md-on-layout Expression to evaluate after layout. Event
   *     object is available as `$event`, and contains performance information.
   *
   * @usage
   * Basic:
   * <hljs lang="html">
   * <md-grid-list md-cols="5" md-gutter="1em" md-row-height="4:3">
   *   <md-grid-tile></md-grid-tile>
   * </md-grid-list>
   * </hljs>
   *
   * Fixed-height rows:
   * <hljs lang="html">
   * <md-grid-list md-cols="4" md-row-height="200px" ...>
   *   <md-grid-tile></md-grid-tile>
   * </md-grid-list>
   * </hljs>
   *
   * Fit rows:
   * <hljs lang="html">
   * <md-grid-list md-cols="4" md-row-height="fit" style="height: 400px;" ...>
   *   <md-grid-tile></md-grid-tile>
   * </md-grid-list>
   * </hljs>
   *
   * Using responsive attributes:
   * <hljs lang="html">
   * <md-grid-list
   *     md-cols-sm="2"
   *     md-cols-md="4"
   *     md-cols-lg="8"
   *     md-cols-gt-lg="12"
   *     ...>
   *   <md-grid-tile></md-grid-tile>
   * </md-grid-list>
   * </hljs>
   */
  function GridListDirective($interpolate, $mdConstant, $mdGridLayout, $mdMedia) {
    return {
      restrict: 'E',
      controller: GridListController,
      scope: {
        mdOnLayout: '&'
      },
      link: postLink
    };
  
    function postLink(scope, element, attrs, ctrl) {
      // Apply semantics
      element.attr('role', 'list');
  
      // Provide the controller with a way to trigger layouts.
      ctrl.layoutDelegate = layoutDelegate;
  
      var invalidateLayout = angular.bind(ctrl, ctrl.invalidateLayout),
          unwatchAttrs = watchMedia();
        scope.$on('$destroy', unwatchMedia);
  
      /**
       * Watches for changes in media, invalidating layout as necessary.
       */
      function watchMedia() {
        for (var mediaName in $mdConstant.MEDIA) {
          $mdMedia(mediaName); // initialize
          $mdMedia.getQuery($mdConstant.MEDIA[mediaName])
              .addListener(invalidateLayout);
        }
        return $mdMedia.watchResponsiveAttributes(
            ['md-cols', 'md-row-height', 'md-gutter'], attrs, layoutIfMediaMatch);
      }
  
      function unwatchMedia() {
        ctrl.layoutDelegate = angular.noop;
  
        unwatchAttrs();
        for (var mediaName in $mdConstant.MEDIA) {
          $mdMedia.getQuery($mdConstant.MEDIA[mediaName])
              .removeListener(invalidateLayout);
        }
      }
  
      /**
       * Performs grid layout if the provided mediaName matches the currently
       * active media type.
       */
      function layoutIfMediaMatch(mediaName) {
        if (mediaName == null) {
          // TODO(shyndman): It would be nice to only layout if we have
          // instances of attributes using this media type
          ctrl.invalidateLayout();
        } else if ($mdMedia(mediaName)) {
          ctrl.invalidateLayout();
        }
      }
  
      var lastLayoutProps;
  
      /**
       * Invokes the layout engine, and uses its results to lay out our
       * tile elements.
       *
       * @param {boolean} tilesInvalidated Whether tiles have been
       *    added/removed/moved since the last layout. This is to avoid situations
       *    where tiles are replaced with properties identical to their removed
       *    counterparts.
       */
      function layoutDelegate(tilesInvalidated) {
        var tiles = getTileElements();
        var props = {
          tileSpans: getTileSpans(tiles),
          colCount: getColumnCount(),
          rowMode: getRowMode(),
          rowHeight: getRowHeight(),
          gutter: getGutter()
        };
  
        if (!tilesInvalidated && angular.equals(props, lastLayoutProps)) {
          return;
        }
  
        var performance =
          $mdGridLayout(props.colCount, props.tileSpans, tiles)
            .map(function(tilePositions, rowCount) {
              return {
                grid: {
                  element: element,
                  style: getGridStyle(props.colCount, rowCount,
                      props.gutter, props.rowMode, props.rowHeight)
                },
                tiles: tilePositions.map(function(ps, i) {
                  return {
                    element: angular.element(tiles[i]),
                    style: getTileStyle(ps.position, ps.spans,
                        props.colCount, rowCount,
                        props.gutter, props.rowMode, props.rowHeight)
                  }
                })
              }
            })
            .reflow()
            .performance();
  
        // Report layout
        scope.mdOnLayout({
          $event: {
            performance: performance
          }
        });
  
        lastLayoutProps = props;
      }
  
      // Use $interpolate to do some simple string interpolation as a convenience.
  
      var startSymbol = $interpolate.startSymbol();
      var endSymbol = $interpolate.endSymbol();
  
      // Returns an expression wrapped in the interpolator's start and end symbols.
      function expr(exprStr) {
        return startSymbol + exprStr + endSymbol;
      }
  
      // The amount of space a single 1x1 tile would take up (either width or height), used as
      // a basis for other calculations. This consists of taking the base size percent (as would be
      // if evenly dividing the size between cells), and then subtracting the size of one gutter.
      // However, since there are no gutters on the edges, each tile only uses a fration
      // (gutterShare = numGutters / numCells) of the gutter size. (Imagine having one gutter per
      // tile, and then breaking up the extra gutter on the edge evenly among the cells).
      var UNIT = $interpolate(expr('share') + '% - (' + expr('gutter') + ' * ' + expr('gutterShare') + ')');
  
      // The horizontal or vertical position of a tile, e.g., the 'top' or 'left' property value.
      // The position comes the size of a 1x1 tile plus gutter for each previous tile in the
      // row/column (offset).
      var POSITION  = $interpolate('calc((' + expr('unit') + ' + ' + expr('gutter') + ') * ' + expr('offset') + ')');
  
      // The actual size of a tile, e.g., width or height, taking rowSpan or colSpan into account.
      // This is computed by multiplying the base unit by the rowSpan/colSpan, and then adding back
      // in the space that the gutter would normally have used (which was already accounted for in
      // the base unit calculation).
      var DIMENSION = $interpolate('calc((' + expr('unit') + ') * ' + expr('span') + ' + (' + expr('span') + ' - 1) * ' + expr('gutter') + ')');
  
      /**
       * Gets the styles applied to a tile element described by the given parameters.
       * @param {{row: number, col: number}} position The row and column indices of the tile.
       * @param {{row: number, col: number}} spans The rowSpan and colSpan of the tile.
       * @param {number} colCount The number of columns.
       * @param {number} rowCount The number of rows.
       * @param {string} gutter The amount of space between tiles. This will be something like
       *     '5px' or '2em'.
       * @param {string} rowMode The row height mode. Can be one of:
       *     'fixed': all rows have a fixed size, given by rowHeight,
       *     'ratio': row height defined as a ratio to width, or
       *     'fit': fit to the grid-list element height, divinding evenly among rows.
       * @param {string|number} rowHeight The height of a row. This is only used for 'fixed' mode and
       *     for 'ratio' mode. For 'ratio' mode, this is the *ratio* of width-to-height (e.g., 0.75).
       * @returns {Object} Map of CSS properties to be applied to the style element. Will define
       *     values for top, left, width, height, marginTop, and paddingTop.
       */
      function getTileStyle(position, spans, colCount, rowCount, gutter, rowMode, rowHeight) {
        // TODO(shyndman): There are style caching opportunities here.
  
        // Percent of the available horizontal space that one column takes up.
        var hShare = (1 / colCount) * 100;
  
        // Fraction of the gutter size that each column takes up.
        var hGutterShare = (colCount - 1) / colCount;
  
        // Base horizontal size of a column.
        var hUnit = UNIT({share: hShare, gutterShare: hGutterShare, gutter: gutter});
  
        // The width and horizontal position of each tile is always calculated the same way, but the
        // height and vertical position depends on the rowMode.
        var style = {
          left: POSITION({ unit: hUnit, offset: position.col, gutter: gutter }),
          width: DIMENSION({ unit: hUnit, span: spans.col, gutter: gutter }),
          // resets
          paddingTop: '',
          marginTop: '',
          top: '',
          height: ''
        };
  
        switch (rowMode) {
          case 'fixed':
            // In fixed mode, simply use the given rowHeight.
            style.top = POSITION({ unit: rowHeight, offset: position.row, gutter: gutter });
            style.height = DIMENSION({ unit: rowHeight, span: spans.row, gutter: gutter });
            break;
  
          case 'ratio':
            // Percent of the available vertical space that one row takes up. Here, rowHeight holds
            // the ratio value. For example, if the width:height ratio is 4:3, rowHeight = 1.333.
            var vShare = hShare / rowHeight;
  
            // Base veritcal size of a row.
            var vUnit = UNIT({ share: vShare, gutterShare: hGutterShare, gutter: gutter });
  
            // padidngTop and marginTop are used to maintain the given aspect ratio, as
            // a percentage-based value for these properties is applied to the *width* of the
            // containing block. See http://www.w3.org/TR/CSS2/box.html#margin-properties
            style.paddingTop = DIMENSION({ unit: vUnit, span: spans.row, gutter: gutter});
            style.marginTop = POSITION({ unit: vUnit, offset: position.row, gutter: gutter });
            break;
  
          case 'fit':
            // Fraction of the gutter size that each column takes up.
            var vGutterShare = (rowCount - 1) / rowCount;
  
            // Percent of the available vertical space that one row takes up.
            var vShare = (1 / rowCount) * 100;
  
            // Base vertical size of a row.
            var vUnit = UNIT({share: vShare, gutterShare: vGutterShare, gutter: gutter});
  
            style.top = POSITION({unit: vUnit, offset: position.row, gutter: gutter});
            style.height = DIMENSION({unit: vUnit, span: spans.row, gutter: gutter});
            break;
        }
  
        return style;
      }
  
      function getGridStyle(colCount, rowCount, gutter, rowMode, rowHeight) {
        var style = {};
  
        switch(rowMode) {
          case 'fixed':
            style.height = DIMENSION({ unit: rowHeight, span: rowCount, gutter: gutter });
            style.paddingBottom = '';
            break;
  
          case 'ratio':
            // rowHeight is width / height
            var hGutterShare = colCount === 1 ? 0 : (colCount - 1) / colCount,
                hShare = (1 / colCount) * 100,
                vShare = hShare * (1 / rowHeight),
                vUnit = UNIT({ share: vShare, gutterShare: hGutterShare, gutter: gutter });
  
            style.height = '';
            style.paddingBottom = DIMENSION({ unit: vUnit, span: rowCount, gutter: gutter});
            break;
  
          case 'fit':
            // noop, as the height is user set
            break;
        }
  
        return style;
      }
  
      function getTileElements() {
        return [].filter.call(element.children(), function(ele) {
          return ele.tagName == 'MD-GRID-TILE' && !ele.$$mdDestroyed;
        });
      }
  
      /**
       * Gets an array of objects containing the rowspan and colspan for each tile.
       * @returns {Array<{row: number, col: number}>}
       */
      function getTileSpans(tileElements) {
        return [].map.call(tileElements, function(ele) {
          var ctrl = angular.element(ele).controller('mdGridTile');
          return {
            row: parseInt(
                $mdMedia.getResponsiveAttribute(ctrl.$attrs, 'md-rowspan'), 10) || 1,
            col: parseInt(
                $mdMedia.getResponsiveAttribute(ctrl.$attrs, 'md-colspan'), 10) || 1
          };
        });
      }
  
      function getColumnCount() {
        var colCount = parseInt($mdMedia.getResponsiveAttribute(attrs, 'md-cols'), 10);
        if (isNaN(colCount)) {
          throw 'md-grid-list: md-cols attribute was not found, or contained a non-numeric value';
        }
        return colCount;
      }
  
      function getGutter() {
        return applyDefaultUnit($mdMedia.getResponsiveAttribute(attrs, 'md-gutter') || 1);
      }
  
      function getRowHeight() {
        var rowHeight = $mdMedia.getResponsiveAttribute(attrs, 'md-row-height');
        switch (getRowMode()) {
          case 'fixed':
            return applyDefaultUnit(rowHeight);
          case 'ratio':
            var whRatio = rowHeight.split(':');
            return parseFloat(whRatio[0]) / parseFloat(whRatio[1]);
          case 'fit':
            return 0; // N/A
        }
      }
  
      function getRowMode() {
        var rowHeight = $mdMedia.getResponsiveAttribute(attrs, 'md-row-height');
        if (rowHeight == 'fit') {
          return 'fit';
        } else if (rowHeight.indexOf(':') !== -1) {
          return 'ratio';
        } else {
          return 'fixed';
        }
      }
  
      function applyDefaultUnit(val) {
        return /\D$/.test(val) ? val : val + 'px';
      }
    }
  }
  
  /* @ngInject */
  function GridListController($mdUtil) {
    this.layoutInvalidated = false;
    this.tilesInvalidated = false;
    this.$timeout_ = $mdUtil.nextTick;
    this.layoutDelegate = angular.noop;
  }
  
  GridListController.prototype = {
    invalidateTiles: function() {
      this.tilesInvalidated = true;
      this.invalidateLayout();
    },
  
    invalidateLayout: function() {
      if (this.layoutInvalidated) {
        return;
      }
      this.layoutInvalidated = true;
      this.$timeout_(angular.bind(this, this.layout));
    },
  
    layout: function() {
      try {
        this.layoutDelegate(this.tilesInvalidated);
      } finally {
        this.layoutInvalidated = false;
        this.tilesInvalidated = false;
      }
    }
  };
  
  
  /* @ngInject */
  function GridLayoutFactory($mdUtil) {
    var defaultAnimator = GridTileAnimator;
  
    /**
     * Set the reflow animator callback
     */
    GridLayout.animateWith = function(customAnimator) {
      defaultAnimator = !angular.isFunction(customAnimator) ? GridTileAnimator : customAnimator;
    };
  
    return GridLayout;
  
    /**
     * Publish layout function
     */
    function GridLayout(colCount, tileSpans) {
        var self, layoutInfo, gridStyles, layoutTime, mapTime, reflowTime;
  
        layoutTime = $mdUtil.time(function() {
          layoutInfo = calculateGridFor(colCount, tileSpans);
        });
  
        return self = {
  
          /**
           * An array of objects describing each tile's position in the grid.
           */
          layoutInfo: function() {
            return layoutInfo;
          },
  
          /**
           * Maps grid positioning to an element and a set of styles using the
           * provided updateFn.
           */
          map: function(updateFn) {
            mapTime = $mdUtil.time(function() {
              var info = self.layoutInfo();
              gridStyles = updateFn(info.positioning, info.rowCount);
            });
            return self;
          },
  
          /**
           * Default animator simply sets the element.css( <styles> ). An alternate
           * animator can be provided as an argument. The function has the following
           * signature:
           *
           *    function({grid: {element: JQLite, style: Object}, tiles: Array<{element: JQLite, style: Object}>)
           */
          reflow: function(animatorFn) {
            reflowTime = $mdUtil.time(function() {
              var animator = animatorFn || defaultAnimator;
              animator(gridStyles.grid, gridStyles.tiles);
            });
            return self;
          },
  
          /**
           * Timing for the most recent layout run.
           */
          performance: function() {
            return {
              tileCount: tileSpans.length,
              layoutTime: layoutTime,
              mapTime: mapTime,
              reflowTime: reflowTime,
              totalTime: layoutTime + mapTime + reflowTime
            };
          }
        };
      }
  
    /**
     * Default Gridlist animator simple sets the css for each element;
     * NOTE: any transitions effects must be manually set in the CSS.
     * e.g.
     *
     *  md-grid-tile {
     *    transition: all 700ms ease-out 50ms;
     *  }
     *
     */
    function GridTileAnimator(grid, tiles) {
      grid.element.css(grid.style);
      tiles.forEach(function(t) {
        t.element.css(t.style);
      })
    }
  
    /**
     * Calculates the positions of tiles.
     *
     * The algorithm works as follows:
     *    An Array<Number> with length colCount (spaceTracker) keeps track of
     *    available tiling positions, where elements of value 0 represents an
     *    empty position. Space for a tile is reserved by finding a sequence of
     *    0s with length <= than the tile's colspan. When such a space has been
     *    found, the occupied tile positions are incremented by the tile's
     *    rowspan value, as these positions have become unavailable for that
     *    many rows.
     *
     *    If the end of a row has been reached without finding space for the
     *    tile, spaceTracker's elements are each decremented by 1 to a minimum
     *    of 0. Rows are searched in this fashion until space is found.
     */
    function calculateGridFor(colCount, tileSpans) {
      var curCol = 0,
          curRow = 0,
          spaceTracker = newSpaceTracker();
  
      return {
        positioning: tileSpans.map(function(spans, i) {
          return {
            spans: spans,
            position: reserveSpace(spans, i)
          };
        }),
        rowCount: curRow + Math.max.apply(Math, spaceTracker)
      };
  
      function reserveSpace(spans, i) {
        if (spans.col > colCount) {
          throw 'md-grid-list: Tile at position ' + i + ' has a colspan ' +
              '(' + spans.col + ') that exceeds the column count ' +
              '(' + colCount + ')';
        }
  
        var start = 0,
            end = 0;
  
        // TODO(shyndman): This loop isn't strictly necessary if you can
        // determine the minimum number of rows before a space opens up. To do
        // this, recognize that you've iterated across an entire row looking for
        // space, and if so fast-forward by the minimum rowSpan count. Repeat
        // until the required space opens up.
        while (end - start < spans.col) {
          if (curCol >= colCount) {
            nextRow();
            continue;
          }
  
          start = spaceTracker.indexOf(0, curCol);
          if (start === -1 || (end = findEnd(start + 1)) === -1) {
            start = end = 0;
            nextRow();
            continue;
          }
  
          curCol = end + 1;
        }
  
        adjustRow(start, spans.col, spans.row);
        curCol = start + spans.col;
  
        return {
          col: start,
          row: curRow
        };
      }
  
      function nextRow() {
        curCol = 0;
        curRow++;
        adjustRow(0, colCount, -1); // Decrement row spans by one
      }
  
      function adjustRow(from, cols, by) {
        for (var i = from; i < from + cols; i++) {
          spaceTracker[i] = Math.max(spaceTracker[i] + by, 0);
        }
      }
  
      function findEnd(start) {
        var i;
        for (i = start; i < spaceTracker.length; i++) {
          if (spaceTracker[i] !== 0) {
            return i;
          }
        }
  
        if (i === spaceTracker.length) {
          return i;
        }
      }
  
      function newSpaceTracker() {
        var tracker = [];
        for (var i = 0; i < colCount; i++) {
          tracker.push(0);
        }
        return tracker;
      }
    }
  }
  
  /**
   * @ngdoc directive
   * @name mdGridTile
   * @module material.components.gridList
   * @restrict E
   * @description
   * Tiles contain the content of an `md-grid-list`. They span one or more grid
   * cells vertically or horizontally, and use `md-grid-tile-{footer,header}` to
   * display secondary content.
   *
   * ### Responsive Attributes
   *
   * The `md-grid-tile` directive supports "responsive" attributes, which allow
   * different `md-rowspan` and `md-colspan` values depending on the currently
   * matching media query.
   *
   * In order to set a responsive attribute, first define the fallback value with
   * the standard attribute name, then add additional attributes with the
   * following convention: `{base-attribute-name}-{media-query-name}="{value}"`
   * (ie. `md-colspan-sm="4"`)
   *
   * @param {number=} md-colspan The number of columns to span (default 1). Cannot
   *    exceed the number of columns in the grid. Supports interpolation.
   * @param {number=} md-rowspan The number of rows to span (default 1). Supports
   *     interpolation.
   *
   * @usage
   * With header:
   * <hljs lang="html">
   * <md-grid-tile>
   *   <md-grid-tile-header>
   *     <h3>This is a header</h3>
   *   </md-grid-tile-header>
   * </md-grid-tile>
   * </hljs>
   *
   * With footer:
   * <hljs lang="html">
   * <md-grid-tile>
   *   <md-grid-tile-footer>
   *     <h3>This is a footer</h3>
   *   </md-grid-tile-footer>
   * </md-grid-tile>
   * </hljs>
   *
   * Spanning multiple rows/columns:
   * <hljs lang="html">
   * <md-grid-tile md-colspan="2" md-rowspan="3">
   * </md-grid-tile>
   * </hljs>
   *
   * Responsive attributes:
   * <hljs lang="html">
   * <md-grid-tile md-colspan="1" md-colspan-sm="3" md-colspan-md="5">
   * </md-grid-tile>
   * </hljs>
   */
  function GridTileDirective($mdMedia) {
    return {
      restrict: 'E',
      require: '^mdGridList',
      template: '<figure ng-transclude></figure>',
      transclude: true,
      scope: {},
      // Simple controller that exposes attributes to the grid directive
      controller: function($attrs) {
        this.$attrs = $attrs;
      },
      link: postLink
    };
  
    function postLink(scope, element, attrs, gridCtrl) {
      // Apply semantics
      element.attr('role', 'listitem');
  
      // If our colspan or rowspan changes, trigger a layout
      var unwatchAttrs = $mdMedia.watchResponsiveAttributes(['md-colspan', 'md-rowspan'],
          attrs, angular.bind(gridCtrl, gridCtrl.invalidateLayout));
  
      // Tile registration/deregistration
      gridCtrl.invalidateTiles();
      scope.$on('$destroy', function() {
        // Mark the tile as destroyed so it is no longer considered in layout,
        // even if the DOM element sticks around (like during a leave animation)
        element[0].$$mdDestroyed = true;
        unwatchAttrs();
        gridCtrl.invalidateLayout();
      });
  
      if (angular.isDefined(scope.$parent.$index)) {
        scope.$watch(function() { return scope.$parent.$index; },
          function indexChanged(newIdx, oldIdx) {
            if (newIdx === oldIdx) {
              return;
            }
            gridCtrl.invalidateTiles();
          });
      }
    }
  }
  
  
  function GridTileCaptionDirective() {
    return {
      template: '<figcaption ng-transclude></figcaption>',
      transclude: true
    };
  }
  