Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(924)

Unified Diff: polymer_0.5.0/bower_components/core-list/core-list.html

Issue 786953007: npm_modules: Fork bower_components into Polymer 0.4.0 and 0.5.0 versions (Closed) Base URL: https://chromium.googlesource.com/infra/third_party/npm_modules.git@master
Patch Set: Created 5 years, 12 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
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>
« no previous file with comments | « polymer_0.5.0/bower_components/core-list/core-list.css ('k') | polymer_0.5.0/bower_components/core-list/demo.html » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698