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

Side by Side Diff: 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, 11 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 unified diff | Download patch
OLDNEW
(Empty)
1 <!--
2 Copyright (c) 2014 The Polymer Project Authors. All rights reserved.
3 This code may only be used under the BSD style license found at http://polymer.g ithub.io/LICENSE.txt
4 The complete set of authors may be found at http://polymer.github.io/AUTHORS.txt
5 The complete set of contributors may be found at http://polymer.github.io/CONTRI BUTORS.txt
6 Code distributed by Google as part of the polymer project is also
7 subject to an additional IP rights grant found at http://polymer.github.io/PATEN TS.txt
8 -->
9
10 <!--
11 `core-list` displays a virtual, 'infinite' list. The template inside the
12 `core-list` element represents the dom to create for each list item. The
13 `data` property specifies an array of list item data. The `height` property
14 represents the fixed height of a list item (variable height list items are
15 not yet supported).
16
17 `core-list` manages a viewport of data based on the current scroll position.
18 For performance reasons, not every item in the list is rendered at once.
19
20 List item templates should bind to template models of the following structure
21
22 {
23 index: 0, // list index for this item
24 selected: false, // selection state for this item
25 model: { // user data corresponding to data[index]
26 /* user data */
27 }
28 }
29
30 For example, given the following data array:
31
32 [
33 {name: 'Bob', checked: true},
34 {name: 'Tim', checked: false},
35 ...
36 ]
37
38 The following code would render the list (note the `name` and `checked`
39 properties are bound from the `model` object provided to the template
40 scope):
41
42 <core-list data="{{data}}" height="80">
43 <template>
44 <div class="{{ {selected: selected} | tokenList }}">
45 List row: {{index}}, User data from model: {{model.name}}
46 <input type="checkbox" checked="{{model.checked}}">
47 </div>
48 </template>
49 </core-list>
50
51 By default, the list supports selection via tapping. Styling the selection
52 should be done via binding to the `selected` property of each model.
53
54 @group Polymer Core Elements
55 @element core-list
56 -->
57 <link rel="import" href="../polymer/polymer.html">
58 <link rel="import" href="../core-selection/core-selection.html">
59
60 <polymer-element name="core-list" on-tap="{{tapHandler}}" tabindex="-1">
61 <template>
62 <core-selection id="selection" multi="{{multi}}" on-core-select="{{selectedHan dler}}"></core-selection>
63 <link rel="stylesheet" href="core-list.css">
64 <div id="viewport" class="core-list-viewport"><content></content></div>
65 </template>
66 <script>
67 (function() {
68
69 Polymer('core-list', {
70
71 publish: {
72 /**
73 * Fired when an item element is tapped.
74 *
75 * @event core-activate
76 * @param {Object} detail
77 * @param {Object} detail.item the item element
78 */
79
80 /**
81 *
82 * An array of source data for the list to display.
83 *
84 * @attribute data
85 * @type array
86 * @default null
87 */
88 data: null,
89
90 /**
91 *
92 * An optional element on which to listen for scroll events.
93 *
94 * @attribute scrollTarget
95 * @type Element
96 * @default core-list
97 */
98 scrollTarget: null,
99
100 /**
101 *
102 * The height of a list item. `core-list` currently supports only fixed-he ight
103 * list items. This height must be specified via the height property.
104 *
105 * @attribute height
106 * @type number
107 * @default 80
108 */
109 height: 80,
110
111 /**
112 *
113 * The number of extra items rendered above the minimum set required to
114 * fill the list's height.
115 *
116 * @attribute extraItems
117 * @type number
118 * @default 30
119 */
120 extraItems: 30,
121
122 /**
123 *
124 * When true, tapping a row will select the item, placing its data model
125 * in the set of selected items retrievable via the `selection` property.
126 *
127 * Note that tapping focusable elements within the list item will not
128 * result in selection, since they are presumed to have their own action.
129 *
130 * @attribute selectionEnabled
131 * @type {boolean}
132 * @default true
133 */
134 selectionEnabled: true,
135
136 /**
137 *
138 * Set to true to support multiple selection. Note, existing selection
139 * state is maintained only when changing `multi` from `false` to `true`;
140 * it is cleared when changing from `true` to `false`.
141 *
142 * @attribute multi
143 * @type boolean
144 * @default false
145 */
146 multi: false,
147
148 /**
149 *
150 * Data record (or array of records, if `multi: true`) corresponding to
151 * the currently selected set of items.
152 *
153 * @attribute selection
154 * @type {any}
155 * @default null
156 */
157 selection: null
158 },
159
160 // Local cache of scrollTop
161 _scrollTop: 0,
162
163 observe: {
164 'data template scrollTarget': 'initialize',
165 'multi selectionEnabled': '_resetSelection'
166 },
167
168 ready: function() {
169 this._boundScrollHandler = this.scrollHandler.bind(this);
170 this._oldMulti = this.multi;
171 this._oldSelectionEnabled = this.selectionEnabled;
172 },
173
174 attached: function() {
175 this.template = this.querySelector('template');
176 if (!this.template.bindingDelegate) {
177 this.template.bindingDelegate = this.element.syntax;
178 }
179 },
180
181 _resetSelection: function() {
182 if (((this._oldMulti != this.multi) && !this.multi) ||
183 ((this._oldSelectionEnabled != this.selectionEnabled) &&
184 !this.selectionEnabled)) {
185 this._clearSelection();
186 this.refresh(true);
187 } else {
188 this.selection = this.$.selection.getSelection();
189 }
190 this._oldMulti = this.multi;
191 this._oldSelectionEnabled = this.selectionEnabled;
192 },
193
194 // TODO(sorvell): it'd be nice to dispense with 'data' and just use
195 // template repeat's model. However, we need tighter integration
196 // with TemplateBinding for this.
197 initialize: function() {
198 if (!this.template) {
199 return;
200 }
201
202 // TODO(kschaaf): This is currently the only way to know that the array
203 // was mutated as opposed to newly assigned; to be updated with better API
204 if (arguments.length == 1) {
205 var splices = arguments[0];
206 for (var i=0; i<splices.length; i++) {
207 var s = splices[i];
208 for (var j=0; j<s.removed.length; j++) {
209 var d = s.removed[j];
210 this.$.selection.setItemSelected(d, false);
211 }
212 }
213 } else {
214 this._clearSelection();
215 }
216
217 var target = this.scrollTarget || this;
218 if (this._target !== target) {
219 if (this._target) {
220 this._target.removeEventListener('scroll', this._boundScrollHandler, f alse);
221 }
222 this._target = target;
223 this._target.addEventListener('scroll', this._boundScrollHandler, false) ;
224 }
225 // Only use -webkit-overflow-touch from iOS8+, where scroll events are fir ed
226 var ios = navigator.userAgent.match(/iP(?:hone|ad;(?: U;)? CPU) OS (\d+)/) ;
227 if (ios && ios[1] >= 8) {
228 target.style.webkitOverflowScrolling = 'touch';
229 }
230
231 this.initializeData();
232 },
233
234 initializeData: function() {
235 var currentCount = this._physicalCount || 0;
236 var dataLen = this.data && this.data.length || 0;
237 this._visibleCount = Math.ceil(this._target.offsetHeight / this.height);
238 this._physicalCount = Math.min(this._visibleCount + this.extraItems, dataL en);
239 this._physicalCount = Math.max(currentCount, this._physicalCount);
240 this._physicalData = this._physicalData || new Array(this._physicalCount);
241 var needItemInit = false;
242 while (currentCount < this._physicalCount) {
243 this._physicalData[currentCount++] = {};
244 needItemInit = true;
245 }
246 this.template.model = this._physicalData;
247 this.template.setAttribute('repeat', '');
248 if (needItemInit) {
249 this.onMutation(this, this.initializeItems);
250 } else {
251 this.refresh(true);
252 }
253 },
254
255 initializeItems: function() {
256 var currentCount = this._physicalItems && this._physicalItems.length || 0;
257 this._physicalItems = this._physicalItems || new Array(this._physicalCount );
258 for (var i = 0, item = this.template.nextElementSibling;
259 item && i < this._physicalCount;
260 ++i, item = item.nextElementSibling) {
261 this._physicalItems[i] = item;
262 item._transformValue = 0;
263 }
264 this.refresh(true);
265 },
266
267 updateItem: function(virtualIndex, physicalIndex) {
268 var virtualDatum = this.data && this.data[virtualIndex];
269 var physicalDatum = this._physicalData[physicalIndex];
270 physicalDatum.model = virtualDatum;
271 physicalDatum.physicalIndex = physicalIndex;
272 physicalDatum.index = virtualIndex;
273 physicalDatum.selected = this.selectionEnabled && virtualDatum ?
274 this._selectedData.get(virtualDatum) : null;
275 var physicalItem = this._physicalItems[physicalIndex];
276 physicalItem.hidden = !virtualDatum;
277 },
278
279 scrollHandler: function(e, detail) {
280 this._scrollTop = e.detail ? e.detail.target.scrollTop : e.target.scrollTo p;
281 this.refresh(false);
282 },
283
284 /**
285 * Refresh the list at the current scroll position.
286 *
287 * @method refresh
288 */
289 refresh: function(force) {
290 // Check that the array hasn't gotten longer since data was initialized
291 var dataLen = this.data && this.data.length || 0;
292 if (force) {
293 if (this._physicalCount <
294 Math.min(this._visibleCount + this.extraItems, dataLen)) {
295 // Need to add more items; once new data & items are initialized,
296 // refresh will be run again
297 this.initializeData();
298 return;
299 }
300 this._physicalHeight = this.height * this._physicalCount;
301 this.$.viewport.style.height = this.height * dataLen + 'px';
302 }
303
304 var firstVisibleIndex = Math.floor(this._scrollTop / this.height);
305 var visibleMidpoint = firstVisibleIndex + this._visibleCount / 2;
306
307 var firstReifiedIndex = Math.max(0, Math.floor(visibleMidpoint -
308 this._physicalCount / 2));
309 firstReifiedIndex = Math.min(firstReifiedIndex, dataLen -
310 this._physicalCount);
311 firstReifiedIndex = (firstReifiedIndex < 0) ? 0 : firstReifiedIndex;
312
313 var firstPhysicalIndex = firstReifiedIndex % this._physicalCount;
314 var baseVirtualIndex = firstReifiedIndex - firstPhysicalIndex;
315
316 var baseTransformValue = Math.floor(this.height * baseVirtualIndex);
317 var nextTransformValue = Math.floor(baseTransformValue +
318 this._physicalHeight);
319
320 var baseTransformString = 'translate3d(0,' + baseTransformValue + 'px,0)';
321 var nextTransformString = 'translate3d(0,' + nextTransformValue + 'px,0)';
322
323 this.firstPhysicalIndex = firstPhysicalIndex;
324 this.baseVirtualIndex = baseVirtualIndex;
325
326 for (var i = 0; i < firstPhysicalIndex; ++i) {
327 var item = this._physicalItems[i];
328 if (force || item._transformValue != nextTransformValue) {
329 this.updateItem(baseVirtualIndex + this._physicalCount + i, i);
330 setTransform(item, nextTransformString, nextTransformValue);
331 }
332 }
333 for (var i = firstPhysicalIndex; i < this._physicalCount; ++i) {
334 var item = this._physicalItems[i];
335 if (force || item._transformValue != baseTransformValue) {
336 this.updateItem(baseVirtualIndex + i, i);
337 setTransform(item, baseTransformString, baseTransformValue);
338 }
339 }
340 },
341
342 // list selection
343 tapHandler: function(e) {
344 var n = e.target;
345 var p = e.path;
346 if (!this.selectionEnabled || (n === this)) {
347 return;
348 }
349 requestAnimationFrame(function() {
350 // Gambit: only select the item if the tap wasn't on a focusable child
351 // of the list (since anything with its own action should be focusable
352 // and not result in result in list selection). To check this, we
353 // asynchronously check that shadowRoot.activeElement is null, which
354 // means the tapped item wasn't focusable. On polyfill where
355 // activeElement doesn't follow the data-hinding part of the spec, we
356 // can check that document.activeElement is the list itself, which will
357 // catch focus in lieu of the tapped item being focusable, as we make
358 // the list focusable (tabindex="-1") for this purpose. Note we also
359 // allow the list items themselves to be focusable if desired, so those
360 // are excluded as well.
361 var active = window.ShadowDOMPolyfill ?
362 wrap(document.activeElement) : this.shadowRoot.activeElement;
363 if (active && (active != this) && (active.parentElement != this) &&
364 (document.activeElement != document.body)) {
365 return;
366 }
367 // Unfortunately, Safari does not focus certain form controls via mouse,
368 // so we also blacklist input, button, & select
369 // (https://bugs.webkit.org/show_bug.cgi?id=118043)
370 if ((p[0].localName == 'input') ||
371 (p[0].localName == 'button') ||
372 (p[0].localName == 'select')) {
373 return;
374 }
375
376 var model = n.templateInstance && n.templateInstance.model;
377 if (model) {
378 var vi = model.index, pi = model.physicalIndex;
379 var data = this.data[vi], item = this._physicalItems[pi];
380 this.$.selection.select(data);
381 this.asyncFire('core-activate', {data: data, item: item});
382 }
383 }.bind(this));
384 },
385
386 selectedHandler: function(e, detail) {
387 this.selection = this.$.selection.getSelection();
388 var i$ = this.indexesForData(detail.item);
389 // TODO(sorvell): we should be relying on selection to store the
390 // selected data but we want to optimize for lookup.
391 this._selectedData.set(detail.item, detail.isSelected);
392 if (i$.physical >= 0) {
393 this.updateItem(i$.virtual, i$.physical);
394 }
395 },
396
397 /**
398 * Select the list item at the given index.
399 *
400 * @method selectItem
401 * @param {number} index
402 */
403 selectItem: function(index) {
404 if (!this.selectionEnabled) {
405 return;
406 }
407 var data = this.data[index];
408 if (data) {
409 this.$.selection.select(data);
410 }
411 },
412
413 /**
414 * Set the selected state of the list item at the given index.
415 *
416 * @method setItemSelected
417 * @param {number} index
418 * @param {boolean} isSelected
419 */
420 setItemSelected: function(index, isSelected) {
421 var data = this.data[index];
422 if (data) {
423 this.$.selection.setItemSelected(data, isSelected);
424 }
425 },
426
427 indexesForData: function(data) {
428 var virtual = this.data.indexOf(data);
429 var physical = this.virtualToPhysicalIndex(virtual);
430 return { virtual: virtual, physical: physical };
431 },
432
433 virtualToPhysicalIndex: function(index) {
434 for (var i=0, l=this._physicalData.length; i<l; i++) {
435 if (this._physicalData[i].index === index) {
436 return i;
437 }
438 }
439 return -1;
440 },
441
442 /**
443 * Clears the current selection state of the list.
444 *
445 * @method clearSelection
446 */
447 clearSelection: function() {
448 this._clearSelection();
449 this.refresh(true);
450 },
451
452 _clearSelection: function() {
453 this._selectedData = new WeakMap();
454 this.$.selection.clear();
455 this.selection = this.$.selection.getSelection();
456 },
457
458 scrollToItem: function(index) {
459 this.scrollTop = index * this.height;
460 }
461
462 });
463
464 // determine proper transform mechanizm
465 if (document.documentElement.style.transform !== undefined) {
466 var setTransform = function(element, string, value) {
467 element.style.transform = string;
468 element._transformValue = value;
469 }
470 } else {
471 var setTransform = function(element, string, value) {
472 element.style.webkitTransform = string;
473 element._transformValue = value;
474 }
475 }
476
477 })();
478 </script>
479 </polymer-element>
OLDNEW
« no previous file with comments | « bower_components/core-list/core-list.css ('k') | bower_components/core-localstorage/.bower.json » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698