| Index: polymer_0.5.0/bower_components/core-list/core-list.html
|
| diff --git a/polymer_0.5.0/bower_components/core-list/core-list.html b/polymer_0.5.0/bower_components/core-list/core-list.html
|
| new file mode 100644
|
| index 0000000000000000000000000000000000000000..8879092d495784434f2e77af79f37c95041f1f08
|
| --- /dev/null
|
| +++ b/polymer_0.5.0/bower_components/core-list/core-list.html
|
| @@ -0,0 +1,1306 @@
|
| +<!--
|
| +Copyright (c) 2014 The Polymer Project Authors. All rights reserved.
|
| +This code may only be used under the BSD style license found at http://polymer.github.io/LICENSE.txt
|
| +The complete set of authors may be found at http://polymer.github.io/AUTHORS.txt
|
| +The complete set of contributors may be found at http://polymer.github.io/CONTRIBUTORS.txt
|
| +Code distributed by Google as part of the polymer project is also
|
| +subject to an additional IP rights grant found at http://polymer.github.io/PATENTS.txt
|
| +-->
|
| +
|
| +<!--
|
| +`core-list` displays a virtual, 'infinite' list. The template inside the
|
| +`core-list` element represents the DOM to create for each list item. The
|
| +`data` property specifies an array of list item data.
|
| +
|
| +For performance reasons, not every item in the list is rendered at once; instead
|
| +a small subset of actual template elements (enough to fill the viewport) are
|
| +rendered and reused as the user scrolls. As such, it is important that all
|
| +state of the list template be bound to the model driving it, since the view
|
| +may be reused with a new model at any time. Particularly, any state that
|
| +may change as the result of a user interaction with the list item must be
|
| +bound to the model to avoid view state inconsistency.
|
| +
|
| +### Template model
|
| +
|
| +List item templates should bind to template models of the following structure:
|
| +
|
| + {
|
| + index: 0, // data index for this item
|
| + selected: false, // selection state for this item
|
| + model: { // user data corresponding to data[index]
|
| + /* user item data */
|
| + }
|
| + }
|
| +
|
| +For example, given the following data array:
|
| +
|
| + [
|
| + {name: 'Bob', checked: true},
|
| + {name: 'Tim', checked: false},
|
| + ...
|
| + ]
|
| +
|
| +The following code would render the list (note the `name` and `checked`
|
| +properties are bound from the `model` object provided to the template
|
| +scope):
|
| +
|
| + <core-list data="{{data}}">
|
| + <template>
|
| + <div class="row {{ {selected: selected} | tokenList }}">
|
| + List row: {{index}}, User data from model: {{model.name}}
|
| + <input type="checkbox" checked="{{model.checked}}">
|
| + </div>
|
| + </template>
|
| + </core-list>
|
| +
|
| +### Selection
|
| +
|
| +By default, the list supports selection via tapping. Styling selected items
|
| +should be done via binding to the `selected` property of each model (see examples
|
| +above. The data model for the selected item (for single-selection) or array of
|
| +models (for multi-selection) is published to the `selection` property.
|
| +
|
| +### Grouping **(experimental)**
|
| +
|
| +`core-list` supports showing dividers between groups of data by setting the
|
| +`groups` property to an array containing group information. An element with
|
| +a `divider` attribute set should be supplied a the top level of the template
|
| +next to the template item to provide the divider template. The template model
|
| +contains extra fields when `groups` is used, as follows:
|
| +
|
| + {
|
| + index: 0, // data index for this item
|
| + groupIndex: 0, // group index for this item
|
| + groupItemIndex: 0, // index within group for this item
|
| + selected: false, // selection state for this item
|
| + model: { // user data corresponding to data[index]
|
| + /* user item data */
|
| + },
|
| + groupModel: { // user group data corresponding to groups[index]
|
| + /* user group data */
|
| + }
|
| + }
|
| +
|
| +Groups may be specified one of two ways (users should choose the data format
|
| +that closest matches their source data, to avoid the performance impact of
|
| +needing totransform data to fit the required structure):
|
| +
|
| +1. Flat data array - In this scenario, the `data` array is provided as
|
| +a flat list of models. Group lengths are determined by the `length` property
|
| +on each group object, with the `data` property providing user-specified group
|
| +data, typically for binding to dividers. For example:
|
| +
|
| + data = [
|
| + { name: 'Adam' },
|
| + { name: 'Alex' },
|
| + { name: 'Bob' },
|
| + { name: 'Chuck' },
|
| + { name: 'Cathy' },
|
| + ...
|
| + ];
|
| +
|
| + groups = [
|
| + { length: 2, data: { letter: 'A' } },
|
| + { length: 1, data: { letter: 'B' } },
|
| + { length: 2, data: { letter: 'C' } },
|
| + ...
|
| + ];
|
| +
|
| + <core-list data="{{data}}" groups="{{groups}}">
|
| + <template>
|
| + <div divider class="divider">{{groupModel.letter}}</div>
|
| + <div class="item">{{model.name}}</div>
|
| + </template>
|
| + </core-list>
|
| +
|
| +2. Nested data array - In this scenario, the `data` array is a nested
|
| +array of arrays of models, where each array determines the length of the
|
| +group, and the `groups` models provide the user-specified data directly.
|
| +For example:
|
| +
|
| + data = [
|
| + [ { name: 'Adam' }, { name: 'Alex' } ],
|
| + [ { name: 'Bob' } ],
|
| + [ { name: 'Chuck' }, { name: 'Cathy' } ],
|
| + ...
|
| + ];
|
| +
|
| + groups = [
|
| + { letter: 'A' },
|
| + { letter: 'B' },
|
| + { letter: 'C' },
|
| + ...
|
| + ];
|
| +
|
| + <core-list data="{{data}}" groups="{{groups}}">
|
| + <template>
|
| + <div divider class="divider">{{groupModel.letter}}</div>
|
| + <div class="item">{{model.name}}</div>
|
| + </template>
|
| + </core-list>
|
| +
|
| +### Grid layout **(experimental)**
|
| +
|
| +`core-list` supports a grid layout in addition to linear layout by setting
|
| +the `grid` attribute. In this case, the list template item must have both fixed
|
| +width and height (e.g. via CSS), with the desired width of each grid item
|
| +specified by the `width` attribute. Based on this, the number of items
|
| +per row are determined automatically based on the size of the list viewport.
|
| +
|
| +### Non-native scrollers **(experimental)**
|
| +
|
| +By default, core-list assumes the `scrollTarget` (if set) is a native scrollable
|
| +element (e.g. `overflow:auto` or `overflow:y`) that fires the `scroll` event and
|
| +whose scroll position can be read/set via the `scrollTop` property.
|
| +`core-list` provides experimental support for setting `scrollTarget`
|
| +to a custom scroller element (e.g. a JS-based scroller) as long as it provides
|
| +the following abstract API:
|
| +
|
| + - `getScrollTop()` - returns the current scroll position
|
| + - `setScrollTop(y)` - sets the current scroll position
|
| + - Fires a `scroll` event indicating when the scroll position has changed
|
| +
|
| +@group Polymer Core Elements
|
| +@element core-list
|
| +-->
|
| +<link rel="import" href="../polymer/polymer.html">
|
| +<link rel="import" href="../core-selection/core-selection.html">
|
| +<link rel="import" href="../core-resizable/core-resizable.html">
|
| +
|
| +<polymer-element name="core-list" tabindex="-1">
|
| +<template>
|
| + <core-selection id="selection" multi="{{multi}}" on-core-select="{{selectedHandler}}"></core-selection>
|
| + <link rel="stylesheet" href="core-list.css">
|
| + <div id="viewport" class="core-list-viewport"><content></content></div>
|
| +</template>
|
| +<script>
|
| +(function() {
|
| +
|
| + var IOS = navigator.userAgent.match(/iP(?:hone|ad;(?: U;)? CPU) OS (\d+)/);
|
| + var IOS_TOUCH_SCROLLING = IOS && IOS[1] >= 8;
|
| +
|
| + Polymer(Polymer.mixin({
|
| +
|
| + publish: {
|
| + /**
|
| + * Fired when an item element is tapped.
|
| + *
|
| + * @event core-activate
|
| + * @param {Object} detail
|
| + * @param {Object} detail.item the item element
|
| + */
|
| +
|
| + /**
|
| + * An array of source data for the list to display. Elements
|
| + * from this array will be set to the `model` peroperty on each
|
| + * template instance scope for binding.
|
| + *
|
| + * When `groups` is used, this array may either be flat, with
|
| + * the group lengths specified in the `groups` array; otherwise
|
| + * `data` may be specified as an array of arrays, such that the
|
| + * each array in `data` specifies a group. See examples above.
|
| + *
|
| + * @attribute data
|
| + * @type array
|
| + * @default null
|
| + */
|
| + data: null,
|
| +
|
| + /**
|
| + * An array of data conveying information about groupings of items
|
| + * in the `data` array. Elements from this array will be set to the
|
| + * `groupModel` property of each template instance scope for binding.
|
| + *
|
| + * When `groups` is used, template children with the `divider` attribute
|
| + * will be shown above each group. Typically data from the `groupModel`
|
| + * would be bound to dividers.
|
| + *
|
| + * If `data` is specified as a flat array, the `groups` array must
|
| + * contain objects of the format `{ length: n, data: {...} }`, where
|
| + * `length` determines the number of items from the `data` array
|
| + * that should be grouped, and `data` specifies the user data that will
|
| + * be assigned to the `groupModel` property on the template instance
|
| + * scope.
|
| + *
|
| + * If `data` is specified as a nested array of arrays, group lengths
|
| + * are derived from these arrays, so each object in `groups` need only
|
| + * contain the user data to be assigned to `groupModel`.
|
| + *
|
| + * @attribute groups
|
| + * @type array
|
| + * @default null
|
| + */
|
| + groups: null,
|
| +
|
| + /**
|
| + *
|
| + * An optional element on which to listen for scroll events.
|
| + *
|
| + * @attribute scrollTarget
|
| + * @type Element
|
| + * @default core-list
|
| + */
|
| + scrollTarget: null,
|
| +
|
| + /**
|
| + *
|
| + * When true, tapping a row will select the item, placing its data model
|
| + * in the set of selected items retrievable via the `selection` property.
|
| + *
|
| + * Note that tapping focusable elements within the list item will not
|
| + * result in selection, since they are presumed to have their own action.
|
| + *
|
| + * @attribute selectionEnabled
|
| + * @type {boolean}
|
| + * @default true
|
| + */
|
| + selectionEnabled: true,
|
| +
|
| + /**
|
| + *
|
| + * Set to true to support multiple selection. Note, existing selection
|
| + * state is maintained only when changing `multi` from `false` to `true`;
|
| + * it is cleared when changing from `true` to `false`.
|
| + *
|
| + * @attribute multi
|
| + * @type boolean
|
| + * @default false
|
| + */
|
| + multi: false,
|
| +
|
| + /**
|
| + *
|
| + * Data record (or array of records, if `multi: true`) corresponding to
|
| + * the currently selected set of items.
|
| + *
|
| + * @attribute selection
|
| + * @type {any}
|
| + * @default null
|
| + */
|
| + selection: null,
|
| +
|
| + /**
|
| + *
|
| + * When true, the list is rendered as a grid. Grid items must be fixed
|
| + * height and width, with the width of each item specified in the `width`
|
| + * property.
|
| + *
|
| + * @attribute grid
|
| + * @type boolean
|
| + * @default false
|
| + */
|
| + grid: false,
|
| +
|
| + /**
|
| + *
|
| + * When `grid` is used, `width` determines the width of each grid item.
|
| + * This property has no meaning when not in `grid` mode.
|
| + *
|
| + * @attribute width
|
| + * @type number
|
| + * @default null
|
| + */
|
| + width: null,
|
| +
|
| + /**
|
| + * The approximate height of a list item, in pixels. This is used only for determining
|
| + * the number of physical elements to render based on the viewport size
|
| + * of the list. Items themselves may vary in height between each other
|
| + * depending on their data model. There is typically no need to adjust
|
| + * this value unless the average size is much larger or smaller than the default.
|
| + *
|
| + * @attribute height
|
| + * @type number
|
| + * @default 200
|
| + */
|
| + height: 200,
|
| +
|
| + /**
|
| + * The amount of scrolling runway the list keeps rendered, as a factor of
|
| + * the list viewport size. There is typically no need to adjust this value
|
| + * other than for performance tuning. Larger value correspond to more
|
| + * physical elements being rendered.
|
| + *
|
| + * @attribute runwayFactor
|
| + * @type number
|
| + * @default 4
|
| + */
|
| + runwayFactor: 4
|
| +
|
| + },
|
| +
|
| + eventDelegates: {
|
| + tap: 'tapHandler',
|
| + 'core-resize': 'updateSize'
|
| + },
|
| +
|
| + // Local cache of scrollTop
|
| + _scrollTop: 0,
|
| +
|
| + observe: {
|
| + 'isAttached data grid width template scrollTarget': 'initialize',
|
| + 'multi selectionEnabled': '_resetSelection'
|
| + },
|
| +
|
| + ready: function() {
|
| + this._boundScrollHandler = this.scrollHandler.bind(this);
|
| + this._boundPositionItems = this._positionItems.bind(this);
|
| + this._oldMulti = this.multi;
|
| + this._oldSelectionEnabled = this.selectionEnabled;
|
| + this._virtualStart = 0;
|
| + this._virtualCount = 0;
|
| + this._physicalStart = 0;
|
| + this._physicalOffset = 0;
|
| + this._physicalSize = 0;
|
| + this._physicalSizes = [];
|
| + this._physicalAverage = 0;
|
| + this._itemSizes = [];
|
| + this._dividerSizes = [];
|
| + this._repositionedItems = [];
|
| +
|
| + this._aboveSize = 0;
|
| +
|
| + this._nestedGroups = false;
|
| + this._groupStart = 0;
|
| + this._groupStartIndex = 0;
|
| + },
|
| +
|
| + attached: function() {
|
| + this.isAttached = true;
|
| + this.template = this.querySelector('template');
|
| + if (!this.template.bindingDelegate) {
|
| + this.template.bindingDelegate = this.element.syntax;
|
| + }
|
| + this.resizableAttachedHandler();
|
| + },
|
| +
|
| + detached: function() {
|
| + this.isAttached = false;
|
| + if (this._target) {
|
| + this._target.removeEventListener('scroll', this._boundScrollHandler);
|
| + }
|
| + this.resizableDetachedHandler();
|
| + },
|
| +
|
| + /**
|
| + * To be called by the user when the list is manually resized
|
| + * or shown after being hidden.
|
| + *
|
| + * @method updateSize
|
| + */
|
| + updateSize: function() {
|
| + if (!this._positionPending && !this._needItemInit) {
|
| + this._resetIndex(this._getFirstVisibleIndex() || 0);
|
| + this.initialize();
|
| + }
|
| + },
|
| +
|
| + _resetSelection: function() {
|
| + if (((this._oldMulti != this.multi) && !this.multi) ||
|
| + ((this._oldSelectionEnabled != this.selectionEnabled) &&
|
| + !this.selectionEnabled)) {
|
| + this._clearSelection();
|
| + this.refresh();
|
| + } else {
|
| + this.selection = this.$.selection.getSelection();
|
| + }
|
| + this._oldMulti = this.multi;
|
| + this._oldSelectionEnabled = this.selectionEnabled;
|
| + },
|
| +
|
| + // Adjust virtual start index based on changes to backing data
|
| + _adjustVirtualIndex: function(splices, group) {
|
| + if (this._targetSize === 0) {
|
| + return;
|
| + }
|
| + var totalDelta = 0;
|
| + for (var i=0; i<splices.length; i++) {
|
| + var s = splices[i];
|
| + var idx = s.index;
|
| + var gidx, gitem;
|
| + if (group) {
|
| + gidx = this.data.indexOf(group);
|
| + idx += this.virtualIndexForGroup(gidx);
|
| + }
|
| + // We only need to care about changes happening above the current position
|
| + if (idx >= this._virtualStart) {
|
| + break;
|
| + }
|
| + var delta = Math.max(s.addedCount - s.removed.length, idx - this._virtualStart);
|
| + totalDelta += delta;
|
| + this._physicalStart += delta;
|
| + this._virtualStart += delta;
|
| + if (this._grouped) {
|
| + if (group) {
|
| + gitem = s.index;
|
| + } else {
|
| + var g = this.groupForVirtualIndex(s.index);
|
| + gidx = g.group;
|
| + gitem = g.groupIndex;
|
| + }
|
| + if (gidx == this._groupStart && gitem < this._groupStartIndex) {
|
| + this._groupStartIndex += delta;
|
| + }
|
| + }
|
| + }
|
| + // Adjust offset/scroll position based on total number of items changed
|
| + if (this._virtualStart < this._physicalCount) {
|
| + this._resetIndex(this._getFirstVisibleIndex() || 0);
|
| + } else {
|
| + totalDelta = Math.max((totalDelta / this._rowFactor) * this._physicalAverage, -this._physicalOffset);
|
| + this._physicalOffset += totalDelta;
|
| + this._scrollTop = this.setScrollTop(this._scrollTop + totalDelta);
|
| + }
|
| + },
|
| +
|
| + _updateSelection: function(splices) {
|
| + for (var i=0; i<splices.length; i++) {
|
| + var s = splices[i];
|
| + for (var j=0; j<s.removed.length; j++) {
|
| + var d = s.removed[j];
|
| + this.$.selection.setItemSelected(d, false);
|
| + }
|
| + }
|
| + },
|
| +
|
| + groupsChanged: function() {
|
| + if (!!this.groups != this._grouped) {
|
| + this.updateSize();
|
| + }
|
| + },
|
| +
|
| + initialize: function() {
|
| + if (!this.template || !this.isAttached) {
|
| + return;
|
| + }
|
| +
|
| + // TODO(kschaaf): Checking arguments.length currently the only way to
|
| + // know that the array was mutated as opposed to newly assigned; need
|
| + // a better API for Polymer observers
|
| + var splices;
|
| + if (arguments.length == 1) {
|
| + splices = arguments[0];
|
| + if (!this._nestedGroups) {
|
| + this._adjustVirtualIndex(splices);
|
| + }
|
| + this._updateSelection(splices);
|
| + } else {
|
| + this._clearSelection();
|
| + }
|
| +
|
| + // Initialize scroll target
|
| + var target = this.scrollTarget || this;
|
| + if (this._target !== target) {
|
| + this.initializeScrollTarget(target);
|
| + }
|
| +
|
| + // Initialize data
|
| + this.initializeData(splices, false);
|
| + },
|
| +
|
| + initializeScrollTarget: function(target) {
|
| + // Listen for scroll events
|
| + if (this._target) {
|
| + this._target.removeEventListener('scroll', this._boundScrollHandler, false);
|
| + }
|
| + this._target = target;
|
| + target.addEventListener('scroll', this._boundScrollHandler, false);
|
| + // Support for non-native scrollers (must implement abstract API):
|
| + // getScrollTop, setScrollTop, sync
|
| + if ((target != this) && target.setScrollTop && target.getScrollTop) {
|
| + this.setScrollTop = function(val) {
|
| + target.setScrollTop(val);
|
| + return target.getScrollTop();
|
| + };
|
| + this.getScrollTop = target.getScrollTop.bind(target);
|
| + this.syncScroller = target.sync ? target.sync.bind(target) : function() {};
|
| + // Adjusting scroll position on non-native scrollers is risky
|
| + this.adjustPositionAllowed = false;
|
| + } else {
|
| + this.setScrollTop = function(val) {
|
| + target.scrollTop = val;
|
| + return target.scrollTop;
|
| + };
|
| + this.getScrollTop = function() {
|
| + return target.scrollTop;
|
| + };
|
| + this.syncScroller = function() {};
|
| + this.adjustPositionAllowed = true;
|
| + }
|
| + // Only use -webkit-overflow-touch from iOS8+, where scroll events are fired
|
| + if (IOS_TOUCH_SCROLLING) {
|
| + target.style.webkitOverflowScrolling = 'touch';
|
| + // Adjusting scrollTop during iOS momentum scrolling is "no bueno"
|
| + this.adjustPositionAllowed = false;
|
| + }
|
| + // Force overflow as necessary
|
| + this._target.style.willChange = 'transform';
|
| + if (getComputedStyle(this._target).position == 'static') {
|
| + this._target.style.position = 'relative';
|
| + }
|
| + this.style.overflowY = (target == this) ? 'auto' : null;
|
| + },
|
| +
|
| + updateGroupObservers: function(splices) {
|
| + // If we're going from grouped to non-grouped, remove all observers
|
| + if (!this._nestedGroups) {
|
| + if (this._groupObservers && this._groupObservers.length) {
|
| + splices = [{
|
| + index: 0,
|
| + addedCount: 0,
|
| + removed: this._groupObservers
|
| + }];
|
| + } else {
|
| + splices = null;
|
| + }
|
| + }
|
| + // Otherwise, create observers for all groups, unless this is a group splice
|
| + if (this._nestedGroups) {
|
| + splices = splices || [{
|
| + index: 0,
|
| + addedCount: this.data.length,
|
| + removed: []
|
| + }];
|
| + }
|
| + if (splices) {
|
| + var observers = this._groupObservers || [];
|
| + // Apply the splices to the observer array
|
| + for (var i=0; i<splices.length; i++) {
|
| + var s = splices[i], j;
|
| + var args = [s.index, s.removed.length];
|
| + if (s.removed.length) {
|
| + for (j=s.index; j<s.removed.length; j++) {
|
| + observers[j].close();
|
| + }
|
| + }
|
| + if (s.addedCount) {
|
| + for (j=s.index; j<s.addedCount; j++) {
|
| + var o = new ArrayObserver(this.data[j]);
|
| + args.push(o);
|
| + o.open(this.getGroupDataHandler(this.data[j]));
|
| + }
|
| + }
|
| + observers.splice.apply(observers, args);
|
| + }
|
| + this._groupObservers = observers;
|
| + }
|
| + },
|
| +
|
| + getGroupDataHandler: function(group) {
|
| + return function(splices) {
|
| + this.groupDataChanged(splices, group);
|
| + }.bind(this);
|
| + },
|
| +
|
| + groupDataChanged: function(splices, group) {
|
| + this._adjustVirtualIndex(splices, group);
|
| + this._updateSelection(splices);
|
| + this.initializeData(null, true);
|
| + },
|
| +
|
| + initializeData: function(splices, groupUpdate) {
|
| + var i;
|
| +
|
| + // Calculate row-factor for grid layout
|
| + if (this.grid) {
|
| + if (!this.width) {
|
| + throw 'Grid requires the `width` property to be set';
|
| + }
|
| + this._rowFactor = Math.floor(this._target.offsetWidth / this.width) || 1;
|
| + var cs = getComputedStyle(this._target);
|
| + var padding = parseInt(cs.paddingLeft || 0) + parseInt(cs.paddingRight || 0);
|
| + this._rowMargin = (this._target.offsetWidth - (this._rowFactor * this.width) - padding) / 2;
|
| + } else {
|
| + this._rowFactor = 1;
|
| + this._rowMargin = 0;
|
| + }
|
| +
|
| + // Count virtual data size, depending on whether grouping is enabled
|
| + if (!this.data || !this.data.length) {
|
| + this._virtualCount = 0;
|
| + this._grouped = false;
|
| + this._nestedGroups = false;
|
| + } else if (this.groups) {
|
| + this._grouped = true;
|
| + this._nestedGroups = Array.isArray(this.data[0]);
|
| + if (this._nestedGroups) {
|
| + if (this.groups.length != this.data.length) {
|
| + throw 'When using nested grouped data, data.length and groups.length must agree!';
|
| + }
|
| + this._virtualCount = 0;
|
| + for (i=0; i<this.groups.length; i++) {
|
| + this._virtualCount += this.data[i] && this.data[i].length;
|
| + }
|
| + } else {
|
| + this._virtualCount = this.data.length;
|
| + var len = 0;
|
| + for (i=0; i<this.groups.length; i++) {
|
| + len += this.groups[i].length;
|
| + }
|
| + if (len != this.data.length) {
|
| + throw 'When using groups data, the sum of group[n].length\'s and data.length must agree!';
|
| + }
|
| + }
|
| + var g = this.groupForVirtualIndex(this._virtualStart);
|
| + this._groupStart = g.group;
|
| + this._groupStartIndex = g.groupIndex;
|
| + } else {
|
| + this._grouped = false;
|
| + this._nestedGroups = false;
|
| + this._virtualCount = this.data.length;
|
| + }
|
| +
|
| + // Update grouped array observers used when group data is nested
|
| + if (!groupUpdate) {
|
| + this.updateGroupObservers(splices);
|
| + }
|
| +
|
| + // Add physical items up to a max based on data length, viewport size, and extra item overhang
|
| + var currentCount = this._physicalCount || 0;
|
| + var height = this._target.offsetHeight;
|
| + if (!height && this._target.offsetParent) {
|
| + console.warn('core-list must either be sized or be inside an overflow:auto div that is sized');
|
| + }
|
| + this._physicalCount = Math.min(Math.ceil(height / (this._physicalAverage || this.height)) * this.runwayFactor * this._rowFactor, this._virtualCount);
|
| + this._physicalCount = Math.max(currentCount, this._physicalCount);
|
| + this._physicalData = this._physicalData || new Array(this._physicalCount);
|
| + var needItemInit = false;
|
| + while (currentCount < this._physicalCount) {
|
| + var model = this.templateInstance ? Object.create(this.templateInstance.model) : {};
|
| + this._physicalData[currentCount++] = model;
|
| + needItemInit = true;
|
| + }
|
| + this.template.model = this._physicalData;
|
| + this.template.setAttribute('repeat', '');
|
| + this._dir = 0;
|
| +
|
| + // If we've added new items, wait until the template renders then
|
| + // initialize the new items before refreshing
|
| + if (!this._needItemInit) {
|
| + if (needItemInit) {
|
| + this._needItemInit = true;
|
| + this.resetMetrics();
|
| + this.onMutation(this, this.initializeItems);
|
| + } else {
|
| + this.refresh();
|
| + }
|
| + }
|
| + },
|
| +
|
| + initializeItems: function() {
|
| + var currentCount = this._physicalItems && this._physicalItems.length || 0;
|
| + this._physicalItems = this._physicalItems || [new Array(this._physicalCount)];
|
| + this._physicalDividers = this._physicalDividers || new Array(this._physicalCount);
|
| + for (var i = 0, item = this.template.nextElementSibling;
|
| + item && i < this._physicalCount;
|
| + item = item.nextElementSibling) {
|
| + if (item.getAttribute('divider') != null) {
|
| + this._physicalDividers[i] = item;
|
| + } else {
|
| + this._physicalItems[i++] = item;
|
| + }
|
| + }
|
| + this.refresh();
|
| + this._needItemInit = false;
|
| + },
|
| +
|
| + _updateItemData: function(force, physicalIndex, virtualIndex, groupIndex, groupItemIndex) {
|
| + var physicalItem = this._physicalItems[physicalIndex];
|
| + var physicalDatum = this._physicalData[physicalIndex];
|
| + var virtualDatum = this.dataForIndex(virtualIndex, groupIndex, groupItemIndex);
|
| + var needsReposition;
|
| + if (force || physicalDatum.model != virtualDatum) {
|
| + // Set model, index, and selected fields
|
| + physicalDatum.model = virtualDatum;
|
| + physicalDatum.index = virtualIndex;
|
| + physicalDatum.physicalIndex = physicalIndex;
|
| + physicalDatum.selected = this.selectionEnabled && virtualDatum ?
|
| + this._selectedData.get(virtualDatum) : null;
|
| + // Set group-related fields
|
| + if (this._grouped) {
|
| + var groupModel = this.groups[groupIndex];
|
| + physicalDatum.groupModel = groupModel && (this._nestedGroups ? groupModel : groupModel.data);
|
| + physicalDatum.groupIndex = groupIndex;
|
| + physicalDatum.groupItemIndex = groupItemIndex;
|
| + physicalItem._isDivider = this.data.length && (groupItemIndex === 0);
|
| + physicalItem._isRowStart = (groupItemIndex % this._rowFactor) === 0;
|
| + } else {
|
| + physicalDatum.groupModel = null;
|
| + physicalDatum.groupIndex = null;
|
| + physicalDatum.groupItemIndex = null;
|
| + physicalItem._isDivider = false;
|
| + physicalItem._isRowStart = (virtualIndex % this._rowFactor) === 0;
|
| + }
|
| + // Hide physical items when not in use (no model assigned)
|
| + physicalItem.hidden = !virtualDatum;
|
| + var divider = this._physicalDividers[physicalIndex];
|
| + if (divider && (divider.hidden == physicalItem._isDivider)) {
|
| + divider.hidden = !physicalItem._isDivider;
|
| + }
|
| + needsReposition = !force;
|
| + } else {
|
| + needsReposition = false;
|
| + }
|
| + return needsReposition || force;
|
| + },
|
| +
|
| + scrollHandler: function() {
|
| + if (IOS_TOUCH_SCROLLING) {
|
| + // iOS sends multiple scroll events per rAF
|
| + // Align work to rAF to reduce overhead & artifacts
|
| + if (!this._raf) {
|
| + this._raf = requestAnimationFrame(function() {
|
| + this._raf = null;
|
| + this.refresh();
|
| + }.bind(this));
|
| + }
|
| + } else {
|
| + this.refresh();
|
| + }
|
| + },
|
| +
|
| + resetMetrics: function() {
|
| + this._physicalAverage = 0;
|
| + this._physicalAverageCount = 0;
|
| + },
|
| +
|
| + updateMetrics: function(force) {
|
| + // Measure physical items & dividers
|
| + var totalSize = 0;
|
| + var count = 0;
|
| + for (var i=0; i<this._physicalCount; i++) {
|
| + var item = this._physicalItems[i];
|
| + if (!item.hidden) {
|
| + var size = this._itemSizes[i] = item.offsetHeight;
|
| + if (item._isDivider) {
|
| + var divider = this._physicalDividers[i];
|
| + if (divider) {
|
| + size += (this._dividerSizes[i] = divider.offsetHeight);
|
| + }
|
| + }
|
| + this._physicalSizes[i] = size;
|
| + if (item._isRowStart) {
|
| + totalSize += size;
|
| + count++;
|
| + }
|
| + }
|
| + }
|
| + this._physicalSize = totalSize;
|
| +
|
| + // Measure other DOM
|
| + this._viewportSize = this.$.viewport.offsetHeight;
|
| + this._targetSize = this._target.offsetHeight;
|
| +
|
| + // Measure content in scroller before virtualized items
|
| + if (this._target != this) {
|
| + this._aboveSize = this.offsetTop;
|
| + } else {
|
| + this._aboveSize = parseInt(getComputedStyle(this._target).paddingTop);
|
| + }
|
| +
|
| + // Calculate average height
|
| + if (count) {
|
| + totalSize = (this._physicalAverage * this._physicalAverageCount) + totalSize;
|
| + this._physicalAverageCount += count;
|
| + this._physicalAverage = Math.round(totalSize / this._physicalAverageCount);
|
| + }
|
| + },
|
| +
|
| + getGroupLen: function(group) {
|
| + group = arguments.length ? group : this._groupStart;
|
| + if (this._nestedGroups) {
|
| + return this.data[group].length;
|
| + } else {
|
| + return this.groups[group].length;
|
| + }
|
| + },
|
| +
|
| + changeStartIndex: function(inc) {
|
| + this._virtualStart += inc;
|
| + if (this._grouped) {
|
| + while (inc > 0) {
|
| + var groupMax = this.getGroupLen() - this._groupStartIndex - 1;
|
| + if (inc > groupMax) {
|
| + inc -= (groupMax + 1);
|
| + this._groupStart++;
|
| + this._groupStartIndex = 0;
|
| + } else {
|
| + this._groupStartIndex += inc;
|
| + inc = 0;
|
| + }
|
| + }
|
| + while (inc < 0) {
|
| + if (-inc > this._groupStartIndex) {
|
| + inc += this._groupStartIndex;
|
| + this._groupStart--;
|
| + this._groupStartIndex = this.getGroupLen();
|
| + } else {
|
| + this._groupStartIndex += inc;
|
| + inc = this.getGroupLen();
|
| + }
|
| + }
|
| + }
|
| + // In grid mode, virtualIndex must alway start on a row start!
|
| + if (this.grid) {
|
| + if (this._grouped) {
|
| + inc = this._groupStartIndex % this._rowFactor;
|
| + } else {
|
| + inc = this._virtualStart % this._rowFactor;
|
| + }
|
| + if (inc) {
|
| + this.changeStartIndex(-inc);
|
| + }
|
| + }
|
| + },
|
| +
|
| + getRowCount: function(dir) {
|
| + if (!this.grid) {
|
| + return dir;
|
| + } else if (!this._grouped) {
|
| + return dir * this._rowFactor;
|
| + } else {
|
| + if (dir < 0) {
|
| + if (this._groupStartIndex > 0) {
|
| + return -Math.min(this._rowFactor, this._groupStartIndex);
|
| + } else {
|
| + var prevLen = this.getGroupLen(this._groupStart-1);
|
| + return -Math.min(this._rowFactor, prevLen % this._rowFactor || this._rowFactor);
|
| + }
|
| + } else {
|
| + return Math.min(this._rowFactor, this.getGroupLen() - this._groupStartIndex);
|
| + }
|
| + }
|
| + },
|
| +
|
| + _virtualToPhysical: function(virtualIndex) {
|
| + var physicalIndex = (virtualIndex - this._physicalStart) % this._physicalCount;
|
| + return physicalIndex < 0 ? this._physicalCount + physicalIndex : physicalIndex;
|
| + },
|
| +
|
| + groupForVirtualIndex: function(virtual) {
|
| + if (!this._grouped) {
|
| + return {};
|
| + } else {
|
| + var group;
|
| + for (group=0; group<this.groups.length; group++) {
|
| + var groupLen = this.getGroupLen(group);
|
| + if (groupLen > virtual) {
|
| + break;
|
| + } else {
|
| + virtual -= groupLen;
|
| + }
|
| + }
|
| + return {group: group, groupIndex: virtual };
|
| + }
|
| + },
|
| +
|
| + virtualIndexForGroup: function(group, groupIndex) {
|
| + groupIndex = groupIndex ? Math.min(groupIndex, this.getGroupLen(group)) : 0;
|
| + group--;
|
| + while (group >= 0) {
|
| + groupIndex += this.getGroupLen(group--);
|
| + }
|
| + return groupIndex;
|
| + },
|
| +
|
| + dataForIndex: function(virtual, group, groupIndex) {
|
| + if (this.data) {
|
| + if (this._nestedGroups) {
|
| + if (virtual < this._virtualCount) {
|
| + return this.data[group][groupIndex];
|
| + }
|
| + } else {
|
| + return this.data[virtual];
|
| + }
|
| + }
|
| + },
|
| +
|
| + // Refresh the list at the current scroll position.
|
| + refresh: function() {
|
| + var i, deltaCount;
|
| +
|
| + // Determine scroll position & any scrollDelta that may have occurred
|
| + var lastScrollTop = this._scrollTop;
|
| + this._scrollTop = this.getScrollTop();
|
| + var scrollDelta = this._scrollTop - lastScrollTop;
|
| + this._dir = scrollDelta < 0 ? -1 : scrollDelta > 0 ? 1 : 0;
|
| +
|
| + // Adjust virtual items and positioning offset if scroll occurred
|
| + if (Math.abs(scrollDelta) > Math.max(this._physicalSize, this._targetSize)) {
|
| + // Random access to point in list: guess new index based on average size
|
| + deltaCount = Math.round((scrollDelta / this._physicalAverage) * this._rowFactor);
|
| + deltaCount = Math.max(deltaCount, -this._virtualStart);
|
| + deltaCount = Math.min(deltaCount, this._virtualCount - this._virtualStart - 1);
|
| + this._physicalOffset += Math.max(scrollDelta, -this._physicalOffset);
|
| + this.changeStartIndex(deltaCount);
|
| + // console.log(this._scrollTop, 'Random access to ' + this._virtualStart, this._physicalOffset);
|
| + } else {
|
| + // Incremental movement: adjust index by flipping items
|
| + var base = this._aboveSize + this._physicalOffset;
|
| + var margin = 0.3 * Math.max((this._physicalSize - this._targetSize, this._physicalSize));
|
| + this._upperBound = base + margin;
|
| + this._lowerBound = base + this._physicalSize - this._targetSize - margin;
|
| + var flipBound = this._dir > 0 ? this._upperBound : this._lowerBound;
|
| + if (((this._dir > 0 && this._scrollTop > flipBound) ||
|
| + (this._dir < 0 && this._scrollTop < flipBound))) {
|
| + var flipSize = Math.abs(this._scrollTop - flipBound);
|
| + for (i=0; (i<this._physicalCount) && (flipSize > 0) &&
|
| + ((this._dir < 0 && this._virtualStart > 0) ||
|
| + (this._dir > 0 && this._virtualStart < this._virtualCount-this._physicalCount)); i++) {
|
| + var idx = this._virtualToPhysical(this._dir > 0 ?
|
| + this._virtualStart :
|
| + this._virtualStart + this._physicalCount -1);
|
| + var size = this._physicalSizes[idx];
|
| + flipSize -= size;
|
| + var cnt = this.getRowCount(this._dir);
|
| + // console.log(this._scrollTop, 'flip ' + (this._dir > 0 ? 'down' : 'up'), cnt, this._virtualStart, this._physicalOffset);
|
| + if (this._dir > 0) {
|
| + // When scrolling down, offset is adjusted based on previous item's size
|
| + this._physicalOffset += size;
|
| + // console.log(' ->', this._virtualStart, size, this._physicalOffset);
|
| + }
|
| + this.changeStartIndex(cnt);
|
| + if (this._dir < 0) {
|
| + this._repositionedItems.push(this._virtualStart);
|
| + }
|
| + }
|
| + }
|
| + }
|
| +
|
| + // Assign data to items lazily if scrolling, otherwise force
|
| + if (this._updateItems(!scrollDelta)) {
|
| + // Position items after bindings resolve (method varies based on O.o impl)
|
| + if (Observer.hasObjectObserve) {
|
| + this.async(this._boundPositionItems);
|
| + } else {
|
| + Platform.flush();
|
| + Platform.endOfMicrotask(this._boundPositionItems);
|
| + }
|
| + }
|
| + },
|
| +
|
| + _updateItems: function(force) {
|
| + var i, virtualIndex, physicalIndex;
|
| + var needsReposition = false;
|
| + var groupIndex = this._groupStart;
|
| + var groupItemIndex = this._groupStartIndex;
|
| + for (i = 0; i < this._physicalCount; ++i) {
|
| + virtualIndex = this._virtualStart + i;
|
| + physicalIndex = this._virtualToPhysical(virtualIndex);
|
| + // Update physical item with new user data and list metadata
|
| + needsReposition =
|
| + this._updateItemData(force, physicalIndex, virtualIndex, groupIndex, groupItemIndex) || needsReposition;
|
| + // Increment
|
| + groupItemIndex++;
|
| + if (this.groups && groupIndex < this.groups.length - 1) {
|
| + if (groupItemIndex >= this.getGroupLen(groupIndex)) {
|
| + groupItemIndex = 0;
|
| + groupIndex++;
|
| + }
|
| + }
|
| + }
|
| + return needsReposition;
|
| + },
|
| +
|
| + _positionItems: function() {
|
| + var i, virtualIndex, physicalIndex, physicalItem;
|
| +
|
| + // Measure
|
| + this.updateMetrics();
|
| +
|
| + // Pre-positioning tasks
|
| + if (this._dir < 0) {
|
| + // When going up, remove offset after measuring size for
|
| + // new data for item being moved from bottom to top
|
| + while (this._repositionedItems.length) {
|
| + virtualIndex = this._repositionedItems.pop();
|
| + physicalIndex = this._virtualToPhysical(virtualIndex);
|
| + this._physicalOffset -= this._physicalSizes[physicalIndex];
|
| + // console.log(' <-', virtualIndex, this._physicalSizes[physicalIndex], this._physicalOffset);
|
| + }
|
| + // Adjust scroll position to home into top when going up
|
| + if (this._scrollTop + this._targetSize < this._viewportSize) {
|
| + this._updateScrollPosition(this._scrollTop);
|
| + }
|
| + }
|
| +
|
| + // Position items
|
| + var divider, upperBound, lowerBound;
|
| + var rowx = 0;
|
| + var x = this._rowMargin;
|
| + var y = this._physicalOffset;
|
| + var lastHeight = 0;
|
| + for (i = 0; i < this._physicalCount; ++i) {
|
| + // Calculate indices
|
| + virtualIndex = this._virtualStart + i;
|
| + physicalIndex = this._virtualToPhysical(virtualIndex);
|
| + physicalItem = this._physicalItems[physicalIndex];
|
| + // Position divider
|
| + if (physicalItem._isDivider) {
|
| + if (rowx !== 0) {
|
| + y += lastHeight;
|
| + rowx = 0;
|
| + }
|
| + divider = this._physicalDividers[physicalIndex];
|
| + x = this._rowMargin;
|
| + if (divider && (divider._translateX != x || divider._translateY != y)) {
|
| + divider.style.opacity = 1;
|
| + if (this.grid) {
|
| + divider.style.width = this.width * this._rowFactor + 'px';
|
| + }
|
| + divider.style.transform = divider.style.webkitTransform =
|
| + 'translate3d(' + x + 'px,' + y + 'px,0)';
|
| + divider._translateX = x;
|
| + divider._translateY = y;
|
| + }
|
| + y += this._dividerSizes[physicalIndex];
|
| + }
|
| + // Position item
|
| + if (physicalItem._translateX != x || physicalItem._translateY != y) {
|
| + physicalItem.style.opacity = 1;
|
| + physicalItem.style.transform = physicalItem.style.webkitTransform =
|
| + 'translate3d(' + x + 'px,' + y + 'px,0)';
|
| + physicalItem._translateX = x;
|
| + physicalItem._translateY = y;
|
| + }
|
| + // Increment offsets
|
| + lastHeight = this._itemSizes[physicalIndex];
|
| + if (this.grid) {
|
| + rowx++;
|
| + if (rowx >= this._rowFactor) {
|
| + rowx = 0;
|
| + y += lastHeight;
|
| + }
|
| + x = this._rowMargin + rowx * this.width;
|
| + } else {
|
| + y += lastHeight;
|
| + }
|
| + }
|
| +
|
| + if (this._scrollTop >= 0) {
|
| + this._updateViewportHeight();
|
| + }
|
| + },
|
| +
|
| + _updateViewportHeight: function() {
|
| + var remaining = Math.max(this._virtualCount - this._virtualStart - this._physicalCount, 0);
|
| + remaining = Math.ceil(remaining / this._rowFactor);
|
| + var vs = this._physicalOffset + this._physicalSize + remaining * this._physicalAverage;
|
| + if (this._viewportSize != vs) {
|
| + // console.log(this._scrollTop, 'adjusting viewport height', vs - this._viewportSize, vs);
|
| + this._viewportSize = vs;
|
| + this.$.viewport.style.height = this._viewportSize + 'px';
|
| + this.syncScroller();
|
| + }
|
| + },
|
| +
|
| + _updateScrollPosition: function(scrollTop) {
|
| + var deltaHeight = this._virtualStart === 0 ? this._physicalOffset :
|
| + Math.min(scrollTop + this._physicalOffset, 0);
|
| + if (deltaHeight) {
|
| + // console.log(scrollTop, 'adjusting scroll pos', this._virtualStart, -deltaHeight, scrollTop - deltaHeight);
|
| + if (this.adjustPositionAllowed) {
|
| + this._scrollTop = this.setScrollTop(scrollTop - deltaHeight);
|
| + }
|
| + this._physicalOffset -= deltaHeight;
|
| + }
|
| + },
|
| +
|
| + // list selection
|
| + tapHandler: function(e) {
|
| + var n = e.target;
|
| + var p = e.path;
|
| + if (!this.selectionEnabled || (n === this)) {
|
| + return;
|
| + }
|
| + requestAnimationFrame(function() {
|
| + // Gambit: only select the item if the tap wasn't on a focusable child
|
| + // of the list (since anything with its own action should be focusable
|
| + // and not result in result in list selection). To check this, we
|
| + // asynchronously check that shadowRoot.activeElement is null, which
|
| + // means the tapped item wasn't focusable. On polyfill where
|
| + // activeElement doesn't follow the data-hinding part of the spec, we
|
| + // can check that document.activeElement is the list itself, which will
|
| + // catch focus in lieu of the tapped item being focusable, as we make
|
| + // the list focusable (tabindex="-1") for this purpose. Note we also
|
| + // allow the list items themselves to be focusable if desired, so those
|
| + // are excluded as well.
|
| + var active = window.ShadowDOMPolyfill ?
|
| + wrap(document.activeElement) : this.shadowRoot.activeElement;
|
| + if (active && (active != this) && (active.parentElement != this) &&
|
| + (document.activeElement != document.body)) {
|
| + return;
|
| + }
|
| + // Unfortunately, Safari does not focus certain form controls via mouse,
|
| + // so we also blacklist input, button, & select
|
| + // (https://bugs.webkit.org/show_bug.cgi?id=118043)
|
| + if ((p[0].localName == 'input') ||
|
| + (p[0].localName == 'button') ||
|
| + (p[0].localName == 'select')) {
|
| + return;
|
| + }
|
| +
|
| + var model = n.templateInstance && n.templateInstance.model;
|
| + if (model) {
|
| + var data = this.dataForIndex(model.index, model.groupIndex, model.groupItemIndex);
|
| + var item = this._physicalItems[model.physicalIndex];
|
| + if (!this.multi && data == this.selection) {
|
| + this.$.selection.select(null);
|
| + } else {
|
| + this.$.selection.select(data);
|
| + }
|
| + this.asyncFire('core-activate', {data: data, item: item});
|
| + }
|
| + }.bind(this));
|
| + },
|
| +
|
| + selectedHandler: function(e, detail) {
|
| + this.selection = this.$.selection.getSelection();
|
| + var id = this.indexesForData(detail.item);
|
| + // TODO(sorvell): we should be relying on selection to store the
|
| + // selected data but we want to optimize for lookup.
|
| + this._selectedData.set(detail.item, detail.isSelected);
|
| + if (id.physical >= 0 && id.virtual >= 0) {
|
| + this.refresh();
|
| + }
|
| + },
|
| +
|
| + /**
|
| + * Select the list item at the given index.
|
| + *
|
| + * @method selectItem
|
| + * @param {number} index
|
| + */
|
| + selectItem: function(index) {
|
| + if (!this.selectionEnabled) {
|
| + return;
|
| + }
|
| + var data = this.data[index];
|
| + if (data) {
|
| + this.$.selection.select(data);
|
| + }
|
| + },
|
| +
|
| + /**
|
| + * Set the selected state of the list item at the given index.
|
| + *
|
| + * @method setItemSelected
|
| + * @param {number} index
|
| + * @param {boolean} isSelected
|
| + */
|
| + setItemSelected: function(index, isSelected) {
|
| + var data = this.data[index];
|
| + if (data) {
|
| + this.$.selection.setItemSelected(data, isSelected);
|
| + }
|
| + },
|
| +
|
| + indexesForData: function(data) {
|
| + var virtual = -1;
|
| + var groupsLen = 0;
|
| + if (this._nestedGroups) {
|
| + for (var i=0; i<this.groups.length; i++) {
|
| + virtual = this.data[i].indexOf(data);
|
| + if (virtual < 0) {
|
| + groupsLen += this.data[i].length;
|
| + } else {
|
| + virtual += groupsLen;
|
| + break;
|
| + }
|
| + }
|
| + } else {
|
| + virtual = this.data.indexOf(data);
|
| + }
|
| + var physical = this.virtualToPhysicalIndex(virtual);
|
| + return { virtual: virtual, physical: physical };
|
| + },
|
| +
|
| + virtualToPhysicalIndex: function(index) {
|
| + for (var i=0, l=this._physicalData.length; i<l; i++) {
|
| + if (this._physicalData[i].index === index) {
|
| + return i;
|
| + }
|
| + }
|
| + return -1;
|
| + },
|
| +
|
| + /**
|
| + * Clears the current selection state of the list.
|
| + *
|
| + * @method clearSelection
|
| + */
|
| + clearSelection: function() {
|
| + this._clearSelection();
|
| + this.refresh();
|
| + },
|
| +
|
| + _clearSelection: function() {
|
| + this._selectedData = new WeakMap();
|
| + this.$.selection.clear();
|
| + this.selection = this.$.selection.getSelection();
|
| + },
|
| +
|
| + _getFirstVisibleIndex: function() {
|
| + for (var i=0; i<this._physicalCount; i++) {
|
| + var virtualIndex = this._virtualStart + i;
|
| + var physicalIndex = this._virtualToPhysical(virtualIndex);
|
| + var item = this._physicalItems[physicalIndex];
|
| + if (!item.hidden && item._translateY >= this._scrollTop - this._aboveSize) {
|
| + return virtualIndex;
|
| + }
|
| + }
|
| + },
|
| +
|
| + _resetIndex: function(index) {
|
| + index = Math.min(index, this._virtualCount-1);
|
| + index = Math.max(index, 0);
|
| + this.changeStartIndex(index - this._virtualStart);
|
| + this._scrollTop = this.setScrollTop(this._aboveSize + (index / this._rowFactor) * this._physicalAverage);
|
| + this._physicalOffset = this._scrollTop - this._aboveSize;
|
| + this._dir = 0;
|
| + },
|
| +
|
| + /**
|
| + * Scroll to an item.
|
| + *
|
| + * Note, when grouping is used, the index is based on the
|
| + * total flattened number of items. For scrolling to an item
|
| + * within a group, use the `scrollToGroupItem` API.
|
| + *
|
| + * @method scrollToItem
|
| + * @param {number} index
|
| + */
|
| + scrollToItem: function(index) {
|
| + this.scrollToGroupItem(null, index);
|
| + },
|
| +
|
| + /**
|
| + * Scroll to a group.
|
| + *
|
| + * @method scrollToGroup
|
| + * @param {number} group
|
| + */
|
| + scrollToGroup: function(group) {
|
| + this.scrollToGroupItem(group, 0);
|
| + },
|
| +
|
| + /**
|
| + * Scroll to an item within a group.
|
| + *
|
| + * @method scrollToGroupItem
|
| + * @param {number} group
|
| + * @param {number} index
|
| + */
|
| + scrollToGroupItem: function(group, index) {
|
| + if (group != null) {
|
| + index = this.virtualIndexForGroup(group, index);
|
| + }
|
| + this._resetIndex(index);
|
| + this.refresh();
|
| + }
|
| +
|
| + }, Polymer.CoreResizable));
|
| +
|
| +})();
|
| +</script>
|
| +</polymer-element>
|
|
|