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

Side by Side Diff: chrome/browser/resources/md_downloads/crisper.js

Issue 1681053002: Unrestrict version of PolymerElements/iron-list and update it (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@fix-closure
Patch Set: and vulcanize Created 4 years, 10 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
« no previous file with comments | « no previous file | chrome/browser/resources/md_downloads/vulcanized.html » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
1 // Copyright (c) 2012 The Chromium Authors. All rights reserved. 1 // Copyright (c) 2012 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be 2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file. 3 // found in the LICENSE file.
4 4
5 /** 5 /**
6 * The global object. 6 * The global object.
7 * @type {!Object} 7 * @type {!Object}
8 * @const 8 * @const
9 */ 9 */
10 var global = this; 10 var global = this;
11 11
12 /** @typedef {{eventName: string, uid: number}} */
13 var WebUIListener;
14
12 /** Platform, package, object property, and Event support. **/ 15 /** Platform, package, object property, and Event support. **/
13 var cr = function() { 16 var cr = function() {
14 'use strict'; 17 'use strict';
15 18
16 /** 19 /**
17 * Builds an object structure for the provided namespace path, 20 * Builds an object structure for the provided namespace path,
18 * ensuring that names that already exist are not overwritten. For 21 * ensuring that names that already exist are not overwritten. For
19 * example: 22 * example:
20 * "a.b.c" -> a = {};a.b={};a.b.c={}; 23 * "a.b.c" -> a = {};a.b={};a.b.c={};
21 * @param {string} name Name of the object that this file defines. 24 * @param {string} name Name of the object that this file defines.
(...skipping 328 matching lines...) Expand 10 before | Expand all | Expand 10 after
350 function sendWithPromise(methodName, var_args) { 353 function sendWithPromise(methodName, var_args) {
351 var args = Array.prototype.slice.call(arguments, 1); 354 var args = Array.prototype.slice.call(arguments, 1);
352 return new Promise(function(resolve, reject) { 355 return new Promise(function(resolve, reject) {
353 var id = methodName + '_' + createUid(); 356 var id = methodName + '_' + createUid();
354 chromeSendResolverMap[id] = resolve; 357 chromeSendResolverMap[id] = resolve;
355 chrome.send(methodName, [id].concat(args)); 358 chrome.send(methodName, [id].concat(args));
356 }); 359 });
357 } 360 }
358 361
359 /** 362 /**
360 * A registry of callbacks keyed by event name. Used by addWebUIListener to 363 * A map of maps associating event names with listeners. The 2nd level map
361 * register listeners. 364 * associates a listener ID with the callback function, such that individual
362 * @type {!Object<Array<Function>>} 365 * listeners can be removed from an event without affecting other listeners of
366 * the same event.
367 * @type {!Object<!Object<!Function>>}
363 */ 368 */
364 var webUIListenerMap = Object.create(null); 369 var webUIListenerMap = {};
365 370
366 /** 371 /**
367 * The named method the WebUI handler calls directly when an event occurs. 372 * The named method the WebUI handler calls directly when an event occurs.
368 * The WebUI handler must supply the name of the event as the first argument 373 * The WebUI handler must supply the name of the event as the first argument
369 * of the JS invocation; additionally, the handler may supply any number of 374 * of the JS invocation; additionally, the handler may supply any number of
370 * other arguments that will be forwarded to the listener callbacks. 375 * other arguments that will be forwarded to the listener callbacks.
371 * @param {string} event The name of the event that has occurred. 376 * @param {string} event The name of the event that has occurred.
377 * @param {...*} var_args Additional arguments passed from C++.
372 */ 378 */
373 function webUIListenerCallback(event) { 379 function webUIListenerCallback(event, var_args) {
374 var listenerCallbacks = webUIListenerMap[event]; 380 var eventListenersMap = webUIListenerMap[event];
375 for (var i = 0; i < listenerCallbacks.length; i++) { 381 if (!eventListenersMap) {
376 var callback = listenerCallbacks[i]; 382 // C++ event sent for an event that has no listeners.
377 callback.apply(null, Array.prototype.slice.call(arguments, 1)); 383 // TODO(dpapad): Should a warning be displayed here?
384 return;
385 }
386
387 var args = Array.prototype.slice.call(arguments, 1);
388 for (var listenerId in eventListenersMap) {
389 eventListenersMap[listenerId].apply(null, args);
378 } 390 }
379 } 391 }
380 392
381 /** 393 /**
382 * Registers a listener for an event fired from WebUI handlers. Any number of 394 * Registers a listener for an event fired from WebUI handlers. Any number of
383 * listeners may register for a single event. 395 * listeners may register for a single event.
384 * @param {string} event The event to listen to. 396 * @param {string} eventName The event to listen to.
385 * @param {Function} callback The callback run when the event is fired. 397 * @param {!Function} callback The callback run when the event is fired.
398 * @return {!WebUIListener} An object to be used for removing a listener via
399 * cr.removeWebUIListener. Should be treated as read-only.
386 */ 400 */
387 function addWebUIListener(event, callback) { 401 function addWebUIListener(eventName, callback) {
388 if (event in webUIListenerMap) 402 webUIListenerMap[eventName] = webUIListenerMap[eventName] || {};
389 webUIListenerMap[event].push(callback); 403 var uid = createUid();
390 else 404 webUIListenerMap[eventName][uid] = callback;
391 webUIListenerMap[event] = [callback]; 405 return {eventName: eventName, uid: uid};
406 }
407
408 /**
409 * Removes a listener. Does nothing if the specified listener is not found.
410 * @param {!WebUIListener} listener The listener to be removed (as returned by
411 * addWebUIListener).
412 * @return {boolean} Whether the given listener was found and actually
413 * removed.
414 */
415 function removeWebUIListener(listener) {
416 var listenerExists = webUIListenerMap[listener.eventName] &&
417 webUIListenerMap[listener.eventName][listener.uid];
418 if (listenerExists) {
419 delete webUIListenerMap[listener.eventName][listener.uid];
420 return true;
421 }
422 return false;
392 } 423 }
393 424
394 return { 425 return {
395 addSingletonGetter: addSingletonGetter, 426 addSingletonGetter: addSingletonGetter,
396 createUid: createUid, 427 createUid: createUid,
397 define: define, 428 define: define,
398 defineProperty: defineProperty, 429 defineProperty: defineProperty,
399 dispatchPropertyChange: dispatchPropertyChange, 430 dispatchPropertyChange: dispatchPropertyChange,
400 dispatchSimpleEvent: dispatchSimpleEvent, 431 dispatchSimpleEvent: dispatchSimpleEvent,
401 exportPath: exportPath, 432 exportPath: exportPath,
402 getUid: getUid, 433 getUid: getUid,
403 makePublic: makePublic, 434 makePublic: makePublic,
404 webUIResponse: webUIResponse, 435 PropertyKind: PropertyKind,
436
437 // C++ <-> JS communication related methods.
438 addWebUIListener: addWebUIListener,
439 removeWebUIListener: removeWebUIListener,
405 sendWithPromise: sendWithPromise, 440 sendWithPromise: sendWithPromise,
406 webUIListenerCallback: webUIListenerCallback, 441 webUIListenerCallback: webUIListenerCallback,
407 addWebUIListener: addWebUIListener, 442 webUIResponse: webUIResponse,
408 PropertyKind: PropertyKind,
409 443
410 get doc() { 444 get doc() {
411 return document; 445 return document;
412 }, 446 },
413 447
414 /** Whether we are using a Mac or not. */ 448 /** Whether we are using a Mac or not. */
415 get isMac() { 449 get isMac() {
416 return /Mac/.test(navigator.platform); 450 return /Mac/.test(navigator.platform);
417 }, 451 },
418 452
419 /** Whether this is on the Windows platform or not. */ 453 /** Whether this is on the Windows platform or not. */
420 get isWindows() { 454 get isWindows() {
421 return /Win/.test(navigator.platform); 455 return /Win/.test(navigator.platform);
422 }, 456 },
423 457
424 /** Whether this is on chromeOS or not. */ 458 /** Whether this is on chromeOS or not. */
425 get isChromeOS() { 459 get isChromeOS() {
426 return /CrOS/.test(navigator.userAgent); 460 return /CrOS/.test(navigator.userAgent);
427 }, 461 },
428 462
429 /** Whether this is on vanilla Linux (not chromeOS). */ 463 /** Whether this is on vanilla Linux (not chromeOS). */
430 get isLinux() { 464 get isLinux() {
431 return /Linux/.test(navigator.userAgent); 465 return /Linux/.test(navigator.userAgent);
432 }, 466 },
467
468 /** Whether this is on Android. */
469 get isAndroid() {
470 return /Android/.test(navigator.userAgent);
471 }
433 }; 472 };
434 }(); 473 }();
435 // Copyright (c) 2012 The Chromium Authors. All rights reserved. 474 // Copyright (c) 2012 The Chromium Authors. All rights reserved.
436 // Use of this source code is governed by a BSD-style license that can be 475 // Use of this source code is governed by a BSD-style license that can be
437 // found in the LICENSE file. 476 // found in the LICENSE file.
438 477
439 cr.define('cr.ui', function() { 478 cr.define('cr.ui', function() {
440 479
441 /** 480 /**
442 * Decorates elements as an instance of a class. 481 * Decorates elements as an instance of a class.
(...skipping 537 matching lines...) Expand 10 before | Expand all | Expand 10 after
980 1019
981 // <include src="../../../../ui/webui/resources/js/assert.js"> 1020 // <include src="../../../../ui/webui/resources/js/assert.js">
982 1021
983 /** 1022 /**
984 * Alias for document.getElementById. Found elements must be HTMLElements. 1023 * Alias for document.getElementById. Found elements must be HTMLElements.
985 * @param {string} id The ID of the element to find. 1024 * @param {string} id The ID of the element to find.
986 * @return {HTMLElement} The found element or null if not found. 1025 * @return {HTMLElement} The found element or null if not found.
987 */ 1026 */
988 function $(id) { 1027 function $(id) {
989 var el = document.getElementById(id); 1028 var el = document.getElementById(id);
990 var message = 1029 return el ? assertInstanceof(el, HTMLElement) : null;
991 'Element ' + el + ' with id "' + id + '" is not an HTMLElement.'; 1030 }
992 return el ? assertInstanceof(el, HTMLElement, message) : null; 1031
1032 // TODO(devlin): This should return SVGElement, but closure compiler is missing
1033 // those externs.
1034 /**
1035 * Alias for document.getElementById. Found elements must be SVGElements.
1036 * @param {string} id The ID of the element to find.
1037 * @return {Element} The found element or null if not found.
1038 */
1039 function getSVGElement(id) {
1040 var el = document.getElementById(id);
1041 return el ? assertInstanceof(el, Element) : null;
993 } 1042 }
994 1043
995 /** 1044 /**
996 * Add an accessible message to the page that will be announced to 1045 * Add an accessible message to the page that will be announced to
997 * users who have spoken feedback on, but will be invisible to all 1046 * users who have spoken feedback on, but will be invisible to all
998 * other users. It's removed right away so it doesn't clutter the DOM. 1047 * other users. It's removed right away so it doesn't clutter the DOM.
999 * @param {string} msg The text to be pronounced. 1048 * @param {string} msg The text to be pronounced.
1000 */ 1049 */
1001 function announceAccessibleMessage(msg) { 1050 function announceAccessibleMessage(msg) {
1002 var element = document.createElement('div'); 1051 var element = document.createElement('div');
(...skipping 495 matching lines...) Expand 10 before | Expand all | Expand 10 after
1498 } 1547 }
1499 1548
1500 /** 1549 /**
1501 * @param {*} value The value to check. 1550 * @param {*} value The value to check.
1502 * @param {function(new: T, ...)} type A user-defined constructor. 1551 * @param {function(new: T, ...)} type A user-defined constructor.
1503 * @param {string=} opt_message A message to show when this is hit. 1552 * @param {string=} opt_message A message to show when this is hit.
1504 * @return {T} 1553 * @return {T}
1505 * @template T 1554 * @template T
1506 */ 1555 */
1507 function assertInstanceof(value, type, opt_message) { 1556 function assertInstanceof(value, type, opt_message) {
1508 assert(value instanceof type, 1557 // We don't use assert immediately here so that we avoid constructing an error
1509 opt_message || value + ' is not a[n] ' + (type.name || typeof type)); 1558 // message if we don't have to.
1559 if (!(value instanceof type)) {
1560 assertNotReached(opt_message || 'Value ' + value +
1561 ' is not a[n] ' + (type.name || typeof type));
1562 }
1510 return value; 1563 return value;
1511 }; 1564 };
1512 // Copyright 2015 The Chromium Authors. All rights reserved. 1565 // Copyright 2015 The Chromium Authors. All rights reserved.
1513 // Use of this source code is governed by a BSD-style license that can be 1566 // Use of this source code is governed by a BSD-style license that can be
1514 // found in the LICENSE file. 1567 // found in the LICENSE file.
1515 1568
1516 cr.define('downloads', function() { 1569 cr.define('downloads', function() {
1517 /** 1570 /**
1518 * @param {string} chromeSendName 1571 * @param {string} chromeSendName
1519 * @return {function(string):void} A chrome.send() callback with curried name. 1572 * @return {function(string):void} A chrome.send() callback with curried name.
(...skipping 452 matching lines...) Expand 10 before | Expand all | Expand 10 after
1972 if (!this.isAttached) { 2025 if (!this.isAttached) {
1973 return; 2026 return;
1974 } 2027 }
1975 2028
1976 this._notifyingDescendant = true; 2029 this._notifyingDescendant = true;
1977 descendant.notifyResize(); 2030 descendant.notifyResize();
1978 this._notifyingDescendant = false; 2031 this._notifyingDescendant = false;
1979 } 2032 }
1980 }; 2033 };
1981 (function() { 2034 (function() {
2035 'use strict';
2036
2037 /**
2038 * Chrome uses an older version of DOM Level 3 Keyboard Events
2039 *
2040 * Most keys are labeled as text, but some are Unicode codepoints.
2041 * Values taken from: http://www.w3.org/TR/2007/WD-DOM-Level-3-Events-200712 21/keyset.html#KeySet-Set
2042 */
2043 var KEY_IDENTIFIER = {
2044 'U+0008': 'backspace',
2045 'U+0009': 'tab',
2046 'U+001B': 'esc',
2047 'U+0020': 'space',
2048 'U+007F': 'del'
2049 };
2050
2051 /**
2052 * Special table for KeyboardEvent.keyCode.
2053 * KeyboardEvent.keyIdentifier is better, and KeyBoardEvent.key is even bett er
2054 * than that.
2055 *
2056 * Values from: https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEve nt.keyCode#Value_of_keyCode
2057 */
2058 var KEY_CODE = {
2059 8: 'backspace',
2060 9: 'tab',
2061 13: 'enter',
2062 27: 'esc',
2063 33: 'pageup',
2064 34: 'pagedown',
2065 35: 'end',
2066 36: 'home',
2067 32: 'space',
2068 37: 'left',
2069 38: 'up',
2070 39: 'right',
2071 40: 'down',
2072 46: 'del',
2073 106: '*'
2074 };
2075
2076 /**
2077 * MODIFIER_KEYS maps the short name for modifier keys used in a key
2078 * combo string to the property name that references those same keys
2079 * in a KeyboardEvent instance.
2080 */
2081 var MODIFIER_KEYS = {
2082 'shift': 'shiftKey',
2083 'ctrl': 'ctrlKey',
2084 'alt': 'altKey',
2085 'meta': 'metaKey'
2086 };
2087
2088 /**
2089 * KeyboardEvent.key is mostly represented by printable character made by
2090 * the keyboard, with unprintable keys labeled nicely.
2091 *
2092 * However, on OS X, Alt+char can make a Unicode character that follows an
2093 * Apple-specific mapping. In this case, we fall back to .keyCode.
2094 */
2095 var KEY_CHAR = /[a-z0-9*]/;
2096
2097 /**
2098 * Matches a keyIdentifier string.
2099 */
2100 var IDENT_CHAR = /U\+/;
2101
2102 /**
2103 * Matches arrow keys in Gecko 27.0+
2104 */
2105 var ARROW_KEY = /^arrow/;
2106
2107 /**
2108 * Matches space keys everywhere (notably including IE10's exceptional name
2109 * `spacebar`).
2110 */
2111 var SPACE_KEY = /^space(bar)?/;
2112
2113 /**
2114 * Transforms the key.
2115 * @param {string} key The KeyBoardEvent.key
2116 * @param {Boolean} [noSpecialChars] Limits the transformation to
2117 * alpha-numeric characters.
2118 */
2119 function transformKey(key, noSpecialChars) {
2120 var validKey = '';
2121 if (key) {
2122 var lKey = key.toLowerCase();
2123 if (lKey === ' ' || SPACE_KEY.test(lKey)) {
2124 validKey = 'space';
2125 } else if (lKey.length == 1) {
2126 if (!noSpecialChars || KEY_CHAR.test(lKey)) {
2127 validKey = lKey;
2128 }
2129 } else if (ARROW_KEY.test(lKey)) {
2130 validKey = lKey.replace('arrow', '');
2131 } else if (lKey == 'multiply') {
2132 // numpad '*' can map to Multiply on IE/Windows
2133 validKey = '*';
2134 } else {
2135 validKey = lKey;
2136 }
2137 }
2138 return validKey;
2139 }
2140
2141 function transformKeyIdentifier(keyIdent) {
2142 var validKey = '';
2143 if (keyIdent) {
2144 if (keyIdent in KEY_IDENTIFIER) {
2145 validKey = KEY_IDENTIFIER[keyIdent];
2146 } else if (IDENT_CHAR.test(keyIdent)) {
2147 keyIdent = parseInt(keyIdent.replace('U+', '0x'), 16);
2148 validKey = String.fromCharCode(keyIdent).toLowerCase();
2149 } else {
2150 validKey = keyIdent.toLowerCase();
2151 }
2152 }
2153 return validKey;
2154 }
2155
2156 function transformKeyCode(keyCode) {
2157 var validKey = '';
2158 if (Number(keyCode)) {
2159 if (keyCode >= 65 && keyCode <= 90) {
2160 // ascii a-z
2161 // lowercase is 32 offset from uppercase
2162 validKey = String.fromCharCode(32 + keyCode);
2163 } else if (keyCode >= 112 && keyCode <= 123) {
2164 // function keys f1-f12
2165 validKey = 'f' + (keyCode - 112);
2166 } else if (keyCode >= 48 && keyCode <= 57) {
2167 // top 0-9 keys
2168 validKey = String(48 - keyCode);
2169 } else if (keyCode >= 96 && keyCode <= 105) {
2170 // num pad 0-9
2171 validKey = String(96 - keyCode);
2172 } else {
2173 validKey = KEY_CODE[keyCode];
2174 }
2175 }
2176 return validKey;
2177 }
2178
2179 /**
2180 * Calculates the normalized key for a KeyboardEvent.
2181 * @param {KeyboardEvent} keyEvent
2182 * @param {Boolean} [noSpecialChars] Set to true to limit keyEvent.key
2183 * transformation to alpha-numeric chars. This is useful with key
2184 * combinations like shift + 2, which on FF for MacOS produces
2185 * keyEvent.key = @
2186 * To get 2 returned, set noSpecialChars = true
2187 * To get @ returned, set noSpecialChars = false
2188 */
2189 function normalizedKeyForEvent(keyEvent, noSpecialChars) {
2190 // Fall back from .key, to .keyIdentifier, to .keyCode, and then to
2191 // .detail.key to support artificial keyboard events.
2192 return transformKey(keyEvent.key, noSpecialChars) ||
2193 transformKeyIdentifier(keyEvent.keyIdentifier) ||
2194 transformKeyCode(keyEvent.keyCode) ||
2195 transformKey(keyEvent.detail.key, noSpecialChars) || '';
2196 }
2197
2198 function keyComboMatchesEvent(keyCombo, event) {
2199 // For combos with modifiers we support only alpha-numeric keys
2200 var keyEvent = normalizedKeyForEvent(event, keyCombo.hasModifiers);
2201 return keyEvent === keyCombo.key &&
2202 (!keyCombo.hasModifiers || (
2203 !!event.shiftKey === !!keyCombo.shiftKey &&
2204 !!event.ctrlKey === !!keyCombo.ctrlKey &&
2205 !!event.altKey === !!keyCombo.altKey &&
2206 !!event.metaKey === !!keyCombo.metaKey)
2207 );
2208 }
2209
2210 function parseKeyComboString(keyComboString) {
2211 if (keyComboString.length === 1) {
2212 return {
2213 combo: keyComboString,
2214 key: keyComboString,
2215 event: 'keydown'
2216 };
2217 }
2218 return keyComboString.split('+').reduce(function(parsedKeyCombo, keyComboP art) {
2219 var eventParts = keyComboPart.split(':');
2220 var keyName = eventParts[0];
2221 var event = eventParts[1];
2222
2223 if (keyName in MODIFIER_KEYS) {
2224 parsedKeyCombo[MODIFIER_KEYS[keyName]] = true;
2225 parsedKeyCombo.hasModifiers = true;
2226 } else {
2227 parsedKeyCombo.key = keyName;
2228 parsedKeyCombo.event = event || 'keydown';
2229 }
2230
2231 return parsedKeyCombo;
2232 }, {
2233 combo: keyComboString.split(':').shift()
2234 });
2235 }
2236
2237 function parseEventString(eventString) {
2238 return eventString.trim().split(' ').map(function(keyComboString) {
2239 return parseKeyComboString(keyComboString);
2240 });
2241 }
2242
2243 /**
2244 * `Polymer.IronA11yKeysBehavior` provides a normalized interface for proces sing
2245 * keyboard commands that pertain to [WAI-ARIA best practices](http://www.w3 .org/TR/wai-aria-practices/#kbd_general_binding).
2246 * The element takes care of browser differences with respect to Keyboard ev ents
2247 * and uses an expressive syntax to filter key presses.
2248 *
2249 * Use the `keyBindings` prototype property to express what combination of k eys
2250 * will trigger the event to fire.
2251 *
2252 * Use the `key-event-target` attribute to set up event handlers on a specif ic
2253 * node.
2254 * The `keys-pressed` event will fire when one of the key combinations set w ith the
2255 * `keys` property is pressed.
2256 *
2257 * @demo demo/index.html
2258 * @polymerBehavior
2259 */
2260 Polymer.IronA11yKeysBehavior = {
2261 properties: {
2262 /**
2263 * The HTMLElement that will be firing relevant KeyboardEvents.
2264 */
2265 keyEventTarget: {
2266 type: Object,
2267 value: function() {
2268 return this;
2269 }
2270 },
2271
2272 /**
2273 * If true, this property will cause the implementing element to
2274 * automatically stop propagation on any handled KeyboardEvents.
2275 */
2276 stopKeyboardEventPropagation: {
2277 type: Boolean,
2278 value: false
2279 },
2280
2281 _boundKeyHandlers: {
2282 type: Array,
2283 value: function() {
2284 return [];
2285 }
2286 },
2287
2288 // We use this due to a limitation in IE10 where instances will have
2289 // own properties of everything on the "prototype".
2290 _imperativeKeyBindings: {
2291 type: Object,
2292 value: function() {
2293 return {};
2294 }
2295 }
2296 },
2297
2298 observers: [
2299 '_resetKeyEventListeners(keyEventTarget, _boundKeyHandlers)'
2300 ],
2301
2302 keyBindings: {},
2303
2304 registered: function() {
2305 this._prepKeyBindings();
2306 },
2307
2308 attached: function() {
2309 this._listenKeyEventListeners();
2310 },
2311
2312 detached: function() {
2313 this._unlistenKeyEventListeners();
2314 },
2315
2316 /**
2317 * Can be used to imperatively add a key binding to the implementing
2318 * element. This is the imperative equivalent of declaring a keybinding
2319 * in the `keyBindings` prototype property.
2320 */
2321 addOwnKeyBinding: function(eventString, handlerName) {
2322 this._imperativeKeyBindings[eventString] = handlerName;
2323 this._prepKeyBindings();
2324 this._resetKeyEventListeners();
2325 },
2326
2327 /**
2328 * When called, will remove all imperatively-added key bindings.
2329 */
2330 removeOwnKeyBindings: function() {
2331 this._imperativeKeyBindings = {};
2332 this._prepKeyBindings();
2333 this._resetKeyEventListeners();
2334 },
2335
2336 keyboardEventMatchesKeys: function(event, eventString) {
2337 var keyCombos = parseEventString(eventString);
2338 for (var i = 0; i < keyCombos.length; ++i) {
2339 if (keyComboMatchesEvent(keyCombos[i], event)) {
2340 return true;
2341 }
2342 }
2343 return false;
2344 },
2345
2346 _collectKeyBindings: function() {
2347 var keyBindings = this.behaviors.map(function(behavior) {
2348 return behavior.keyBindings;
2349 });
2350
2351 if (keyBindings.indexOf(this.keyBindings) === -1) {
2352 keyBindings.push(this.keyBindings);
2353 }
2354
2355 return keyBindings;
2356 },
2357
2358 _prepKeyBindings: function() {
2359 this._keyBindings = {};
2360
2361 this._collectKeyBindings().forEach(function(keyBindings) {
2362 for (var eventString in keyBindings) {
2363 this._addKeyBinding(eventString, keyBindings[eventString]);
2364 }
2365 }, this);
2366
2367 for (var eventString in this._imperativeKeyBindings) {
2368 this._addKeyBinding(eventString, this._imperativeKeyBindings[eventStri ng]);
2369 }
2370
2371 // Give precedence to combos with modifiers to be checked first.
2372 for (var eventName in this._keyBindings) {
2373 this._keyBindings[eventName].sort(function (kb1, kb2) {
2374 var b1 = kb1[0].hasModifiers;
2375 var b2 = kb2[0].hasModifiers;
2376 return (b1 === b2) ? 0 : b1 ? -1 : 1;
2377 })
2378 }
2379 },
2380
2381 _addKeyBinding: function(eventString, handlerName) {
2382 parseEventString(eventString).forEach(function(keyCombo) {
2383 this._keyBindings[keyCombo.event] =
2384 this._keyBindings[keyCombo.event] || [];
2385
2386 this._keyBindings[keyCombo.event].push([
2387 keyCombo,
2388 handlerName
2389 ]);
2390 }, this);
2391 },
2392
2393 _resetKeyEventListeners: function() {
2394 this._unlistenKeyEventListeners();
2395
2396 if (this.isAttached) {
2397 this._listenKeyEventListeners();
2398 }
2399 },
2400
2401 _listenKeyEventListeners: function() {
2402 Object.keys(this._keyBindings).forEach(function(eventName) {
2403 var keyBindings = this._keyBindings[eventName];
2404 var boundKeyHandler = this._onKeyBindingEvent.bind(this, keyBindings);
2405
2406 this._boundKeyHandlers.push([this.keyEventTarget, eventName, boundKeyH andler]);
2407
2408 this.keyEventTarget.addEventListener(eventName, boundKeyHandler);
2409 }, this);
2410 },
2411
2412 _unlistenKeyEventListeners: function() {
2413 var keyHandlerTuple;
2414 var keyEventTarget;
2415 var eventName;
2416 var boundKeyHandler;
2417
2418 while (this._boundKeyHandlers.length) {
2419 // My kingdom for block-scope binding and destructuring assignment..
2420 keyHandlerTuple = this._boundKeyHandlers.pop();
2421 keyEventTarget = keyHandlerTuple[0];
2422 eventName = keyHandlerTuple[1];
2423 boundKeyHandler = keyHandlerTuple[2];
2424
2425 keyEventTarget.removeEventListener(eventName, boundKeyHandler);
2426 }
2427 },
2428
2429 _onKeyBindingEvent: function(keyBindings, event) {
2430 if (this.stopKeyboardEventPropagation) {
2431 event.stopPropagation();
2432 }
2433
2434 // if event has been already prevented, don't do anything
2435 if (event.defaultPrevented) {
2436 return;
2437 }
2438
2439 for (var i = 0; i < keyBindings.length; i++) {
2440 var keyCombo = keyBindings[i][0];
2441 var handlerName = keyBindings[i][1];
2442 if (keyComboMatchesEvent(keyCombo, event)) {
2443 this._triggerKeyHandler(keyCombo, handlerName, event);
2444 // exit the loop if eventDefault was prevented
2445 if (event.defaultPrevented) {
2446 return;
2447 }
2448 }
2449 }
2450 },
2451
2452 _triggerKeyHandler: function(keyCombo, handlerName, keyboardEvent) {
2453 var detail = Object.create(keyCombo);
2454 detail.keyboardEvent = keyboardEvent;
2455 var event = new CustomEvent(keyCombo.event, {
2456 detail: detail,
2457 cancelable: true
2458 });
2459 this[handlerName].call(this, event);
2460 if (event.defaultPrevented) {
2461 keyboardEvent.preventDefault();
2462 }
2463 }
2464 };
2465 })();
2466 /**
2467 * `Polymer.IronScrollTargetBehavior` allows an element to respond to scroll e vents from a
2468 * designated scroll target.
2469 *
2470 * Elements that consume this behavior can override the `_scrollHandler`
2471 * method to add logic on the scroll event.
2472 *
2473 * @demo demo/scrolling-region.html Scrolling Region
2474 * @demo demo/document.html Document Element
2475 * @polymerBehavior
2476 */
2477 Polymer.IronScrollTargetBehavior = {
2478
2479 properties: {
2480
2481 /**
2482 * Specifies the element that will handle the scroll event
2483 * on the behalf of the current element. This is typically a reference to an `Element`,
2484 * but there are a few more posibilities:
2485 *
2486 * ### Elements id
2487 *
2488 *```html
2489 * <div id="scrollable-element" style="overflow-y: auto;">
2490 * <x-element scroll-target="scrollable-element">
2491 * Content
2492 * </x-element>
2493 * </div>
2494 *```
2495 * In this case, `scrollTarget` will point to the outer div element. Alter natively,
2496 * you can set the property programatically:
2497 *
2498 *```js
2499 * appHeader.scrollTarget = document.querySelector('#scrollable-element');
2500 *```
2501 *
2502 * @type {HTMLElement}
2503 */
2504 scrollTarget: {
2505 type: HTMLElement,
2506 value: function() {
2507 return this._defaultScrollTarget;
2508 }
2509 }
2510 },
2511
2512 observers: [
2513 '_scrollTargetChanged(scrollTarget, isAttached)'
2514 ],
2515
2516 _scrollTargetChanged: function(scrollTarget, isAttached) {
2517 // Remove lister to the current scroll target
2518 if (this._oldScrollTarget) {
2519 if (this._oldScrollTarget === this._doc) {
2520 window.removeEventListener('scroll', this._boundScrollHandler);
2521 } else if (this._oldScrollTarget.removeEventListener) {
2522 this._oldScrollTarget.removeEventListener('scroll', this._boundScrollH andler);
2523 }
2524 this._oldScrollTarget = null;
2525 }
2526 if (isAttached) {
2527 // Support element id references
2528 if (typeof scrollTarget === 'string') {
2529
2530 var ownerRoot = Polymer.dom(this).getOwnerRoot();
2531 this.scrollTarget = (ownerRoot && ownerRoot.$) ?
2532 ownerRoot.$[scrollTarget] : Polymer.dom(this.ownerDocument).queryS elector('#' + scrollTarget);
2533
2534 } else if (this._scrollHandler) {
2535
2536 this._boundScrollHandler = this._boundScrollHandler || this._scrollHan dler.bind(this);
2537 // Add a new listener
2538 if (scrollTarget === this._doc) {
2539 window.addEventListener('scroll', this._boundScrollHandler);
2540 if (this._scrollTop !== 0 || this._scrollLeft !== 0) {
2541 this._scrollHandler();
2542 }
2543 } else if (scrollTarget && scrollTarget.addEventListener) {
2544 scrollTarget.addEventListener('scroll', this._boundScrollHandler);
2545 }
2546 this._oldScrollTarget = scrollTarget;
2547 }
2548 }
2549 },
2550
2551 /**
2552 * Runs on every scroll event. Consumer of this behavior may want to overrid e this method.
2553 *
2554 * @protected
2555 */
2556 _scrollHandler: function scrollHandler() {},
2557
2558 /**
2559 * The default scroll target. Consumers of this behavior may want to customi ze
2560 * the default scroll target.
2561 *
2562 * @type {Element}
2563 */
2564 get _defaultScrollTarget() {
2565 return this._doc;
2566 },
2567
2568 /**
2569 * Shortcut for the document element
2570 *
2571 * @type {Element}
2572 */
2573 get _doc() {
2574 return this.ownerDocument.documentElement;
2575 },
2576
2577 /**
2578 * Gets the number of pixels that the content of an element is scrolled upwa rd.
2579 *
2580 * @type {number}
2581 */
2582 get _scrollTop() {
2583 if (this._isValidScrollTarget()) {
2584 return this.scrollTarget === this._doc ? window.pageYOffset : this.scrol lTarget.scrollTop;
2585 }
2586 return 0;
2587 },
2588
2589 /**
2590 * Gets the number of pixels that the content of an element is scrolled to t he left.
2591 *
2592 * @type {number}
2593 */
2594 get _scrollLeft() {
2595 if (this._isValidScrollTarget()) {
2596 return this.scrollTarget === this._doc ? window.pageXOffset : this.scrol lTarget.scrollLeft;
2597 }
2598 return 0;
2599 },
2600
2601 /**
2602 * Sets the number of pixels that the content of an element is scrolled upwa rd.
2603 *
2604 * @type {number}
2605 */
2606 set _scrollTop(top) {
2607 if (this.scrollTarget === this._doc) {
2608 window.scrollTo(window.pageXOffset, top);
2609 } else if (this._isValidScrollTarget()) {
2610 this.scrollTarget.scrollTop = top;
2611 }
2612 },
2613
2614 /**
2615 * Sets the number of pixels that the content of an element is scrolled to t he left.
2616 *
2617 * @type {number}
2618 */
2619 set _scrollLeft(left) {
2620 if (this.scrollTarget === this._doc) {
2621 window.scrollTo(left, window.pageYOffset);
2622 } else if (this._isValidScrollTarget()) {
2623 this.scrollTarget.scrollLeft = left;
2624 }
2625 },
2626
2627 /**
2628 * Scrolls the content to a particular place.
2629 *
2630 * @method scroll
2631 * @param {number} top The top position
2632 * @param {number} left The left position
2633 */
2634 scroll: function(top, left) {
2635 if (this.scrollTarget === this._doc) {
2636 window.scrollTo(top, left);
2637 } else if (this._isValidScrollTarget()) {
2638 this.scrollTarget.scrollTop = top;
2639 this.scrollTarget.scrollLeft = left;
2640 }
2641 },
2642
2643 /**
2644 * Gets the width of the scroll target.
2645 *
2646 * @type {number}
2647 */
2648 get _scrollTargetWidth() {
2649 if (this._isValidScrollTarget()) {
2650 return this.scrollTarget === this._doc ? window.innerWidth : this.scroll Target.offsetWidth;
2651 }
2652 return 0;
2653 },
2654
2655 /**
2656 * Gets the height of the scroll target.
2657 *
2658 * @type {number}
2659 */
2660 get _scrollTargetHeight() {
2661 if (this._isValidScrollTarget()) {
2662 return this.scrollTarget === this._doc ? window.innerHeight : this.scrol lTarget.offsetHeight;
2663 }
2664 return 0;
2665 },
2666
2667 /**
2668 * Returns true if the scroll target is a valid HTMLElement.
2669 *
2670 * @return {boolean}
2671 */
2672 _isValidScrollTarget: function() {
2673 return this.scrollTarget instanceof HTMLElement;
2674 }
2675 };
2676 (function() {
1982 2677
1983 var IOS = navigator.userAgent.match(/iP(?:hone|ad;(?: U;)? CPU) OS (\d+)/); 2678 var IOS = navigator.userAgent.match(/iP(?:hone|ad;(?: U;)? CPU) OS (\d+)/);
1984 var IOS_TOUCH_SCROLLING = IOS && IOS[1] >= 8; 2679 var IOS_TOUCH_SCROLLING = IOS && IOS[1] >= 8;
1985 var DEFAULT_PHYSICAL_COUNT = 3; 2680 var DEFAULT_PHYSICAL_COUNT = 3;
1986 var MAX_PHYSICAL_COUNT = 500; 2681 var MAX_PHYSICAL_COUNT = 500;
2682 var HIDDEN_Y = '-10000px';
1987 2683
1988 Polymer({ 2684 Polymer({
1989 2685
1990 is: 'iron-list', 2686 is: 'iron-list',
1991 2687
1992 properties: { 2688 properties: {
1993 2689
1994 /** 2690 /**
1995 * An array containing items determining how many instances of the templat e 2691 * An array containing items determining how many instances of the templat e
1996 * to stamp and that that each template instance should bind to. 2692 * to stamp and that that each template instance should bind to.
(...skipping 65 matching lines...) Expand 10 before | Expand all | Expand 10 after
2062 */ 2758 */
2063 multiSelection: { 2759 multiSelection: {
2064 type: Boolean, 2760 type: Boolean,
2065 value: false 2761 value: false
2066 } 2762 }
2067 }, 2763 },
2068 2764
2069 observers: [ 2765 observers: [
2070 '_itemsChanged(items.*)', 2766 '_itemsChanged(items.*)',
2071 '_selectionEnabledChanged(selectionEnabled)', 2767 '_selectionEnabledChanged(selectionEnabled)',
2072 '_multiSelectionChanged(multiSelection)' 2768 '_multiSelectionChanged(multiSelection)',
2769 '_setOverflow(scrollTarget)'
2073 ], 2770 ],
2074 2771
2075 behaviors: [ 2772 behaviors: [
2076 Polymer.Templatizer, 2773 Polymer.Templatizer,
2077 Polymer.IronResizableBehavior 2774 Polymer.IronResizableBehavior,
2775 Polymer.IronA11yKeysBehavior,
2776 Polymer.IronScrollTargetBehavior
2078 ], 2777 ],
2079 2778
2080 listeners: { 2779 listeners: {
2081 'iron-resize': '_resizeHandler' 2780 'iron-resize': '_resizeHandler'
2082 }, 2781 },
2083 2782
2783 keyBindings: {
2784 'up': '_didMoveUp',
2785 'down': '_didMoveDown',
2786 'enter': '_didEnter'
2787 },
2788
2084 /** 2789 /**
2085 * The ratio of hidden tiles that should remain in the scroll direction. 2790 * The ratio of hidden tiles that should remain in the scroll direction.
2086 * Recommended value ~0.5, so it will distribute tiles evely in both directi ons. 2791 * Recommended value ~0.5, so it will distribute tiles evely in both directi ons.
2087 */ 2792 */
2088 _ratio: 0.5, 2793 _ratio: 0.5,
2089 2794
2090 /** 2795 /**
2091 * The element that controls the scroll
2092 * @type {?Element}
2093 */
2094 _scroller: null,
2095
2096 /**
2097 * The padding-top value of the `scroller` element 2796 * The padding-top value of the `scroller` element
2098 */ 2797 */
2099 _scrollerPaddingTop: 0, 2798 _scrollerPaddingTop: 0,
2100 2799
2101 /** 2800 /**
2102 * This value is the same as `scrollTop`. 2801 * This value is the same as `scrollTop`.
2103 */ 2802 */
2104 _scrollPosition: 0, 2803 _scrollPosition: 0,
2105 2804
2106 /** 2805 /**
(...skipping 10 matching lines...) Expand all
2117 * The k-th tile that is at the bottom of the scrolling list. 2816 * The k-th tile that is at the bottom of the scrolling list.
2118 */ 2817 */
2119 _physicalEnd: 0, 2818 _physicalEnd: 0,
2120 2819
2121 /** 2820 /**
2122 * The sum of the heights of all the tiles in the DOM. 2821 * The sum of the heights of all the tiles in the DOM.
2123 */ 2822 */
2124 _physicalSize: 0, 2823 _physicalSize: 0,
2125 2824
2126 /** 2825 /**
2127 * The average `offsetHeight` of the tiles observed till now. 2826 * The average `F` of the tiles observed till now.
2128 */ 2827 */
2129 _physicalAverage: 0, 2828 _physicalAverage: 0,
2130 2829
2131 /** 2830 /**
2132 * The number of tiles which `offsetHeight` > 0 observed until now. 2831 * The number of tiles which `offsetHeight` > 0 observed until now.
2133 */ 2832 */
2134 _physicalAverageCount: 0, 2833 _physicalAverageCount: 0,
2135 2834
2136 /** 2835 /**
2137 * The Y position of the item rendered in the `_physicalStart` 2836 * The Y position of the item rendered in the `_physicalStart`
(...skipping 37 matching lines...) Expand 10 before | Expand all | Expand 10 after
2175 */ 2874 */
2176 _physicalItems: null, 2875 _physicalItems: null,
2177 2876
2178 /** 2877 /**
2179 * An array of heights for each item in `_physicalItems` 2878 * An array of heights for each item in `_physicalItems`
2180 * @type {?Array<number>} 2879 * @type {?Array<number>}
2181 */ 2880 */
2182 _physicalSizes: null, 2881 _physicalSizes: null,
2183 2882
2184 /** 2883 /**
2185 * A cached value for the visible index. 2884 * A cached value for the first visible index.
2186 * See `firstVisibleIndex` 2885 * See `firstVisibleIndex`
2187 * @type {?number} 2886 * @type {?number}
2188 */ 2887 */
2189 _firstVisibleIndexVal: null, 2888 _firstVisibleIndexVal: null,
2190 2889
2191 /** 2890 /**
2891 * A cached value for the last visible index.
2892 * See `lastVisibleIndex`
2893 * @type {?number}
2894 */
2895 _lastVisibleIndexVal: null,
2896
2897
2898 /**
2192 * A Polymer collection for the items. 2899 * A Polymer collection for the items.
2193 * @type {?Polymer.Collection} 2900 * @type {?Polymer.Collection}
2194 */ 2901 */
2195 _collection: null, 2902 _collection: null,
2196 2903
2197 /** 2904 /**
2198 * True if the current item list was rendered for the first time 2905 * True if the current item list was rendered for the first time
2199 * after attached. 2906 * after attached.
2200 */ 2907 */
2201 _itemsRendered: false, 2908 _itemsRendered: false,
2202 2909
2203 /** 2910 /**
2204 * The page that is currently rendered. 2911 * The page that is currently rendered.
2205 */ 2912 */
2206 _lastPage: null, 2913 _lastPage: null,
2207 2914
2208 /** 2915 /**
2209 * The max number of pages to render. One page is equivalent to the height o f the list. 2916 * The max number of pages to render. One page is equivalent to the height o f the list.
2210 */ 2917 */
2211 _maxPages: 3, 2918 _maxPages: 3,
2212 2919
2213 /** 2920 /**
2921 * The currently focused item index.
2922 */
2923 _focusedIndex: 0,
2924
2925 /**
2926 * The the item that is focused if it is moved offscreen.
2927 * @private {?TemplatizerNode}
2928 */
2929 _offscreenFocusedItem: null,
2930
2931 /**
2932 * The item that backfills the `_offscreenFocusedItem` in the physical items
2933 * list when that item is moved offscreen.
2934 */
2935 _focusBackfillItem: null,
2936
2937 /**
2214 * The bottom of the physical content. 2938 * The bottom of the physical content.
2215 */ 2939 */
2216 get _physicalBottom() { 2940 get _physicalBottom() {
2217 return this._physicalTop + this._physicalSize; 2941 return this._physicalTop + this._physicalSize;
2218 }, 2942 },
2219 2943
2220 /** 2944 /**
2221 * The bottom of the scroll. 2945 * The bottom of the scroll.
2222 */ 2946 */
2223 get _scrollBottom() { 2947 get _scrollBottom() {
2224 return this._scrollPosition + this._viewportSize; 2948 return this._scrollPosition + this._viewportSize;
2225 }, 2949 },
2226 2950
2227 /** 2951 /**
2228 * The n-th item rendered in the last physical item. 2952 * The n-th item rendered in the last physical item.
2229 */ 2953 */
2230 get _virtualEnd() { 2954 get _virtualEnd() {
2231 return this._virtualStartVal + this._physicalCount - 1; 2955 return this._virtualStart + this._physicalCount - 1;
2232 }, 2956 },
2233 2957
2234 /** 2958 /**
2235 * The lowest n-th value for an item such that it can be rendered in `_physi calStart`. 2959 * The lowest n-th value for an item such that it can be rendered in `_physi calStart`.
2236 */ 2960 */
2237 _minVirtualStart: 0, 2961 _minVirtualStart: 0,
2238 2962
2239 /** 2963 /**
2240 * The largest n-th value for an item such that it can be rendered in `_phys icalStart`. 2964 * The largest n-th value for an item such that it can be rendered in `_phys icalStart`.
2241 */ 2965 */
(...skipping 14 matching lines...) Expand all
2256 get _maxScrollTop() { 2980 get _maxScrollTop() {
2257 return this._estScrollHeight - this._viewportSize; 2981 return this._estScrollHeight - this._viewportSize;
2258 }, 2982 },
2259 2983
2260 /** 2984 /**
2261 * Sets the n-th item rendered in `_physicalStart` 2985 * Sets the n-th item rendered in `_physicalStart`
2262 */ 2986 */
2263 set _virtualStart(val) { 2987 set _virtualStart(val) {
2264 // clamp the value so that _minVirtualStart <= val <= _maxVirtualStart 2988 // clamp the value so that _minVirtualStart <= val <= _maxVirtualStart
2265 this._virtualStartVal = Math.min(this._maxVirtualStart, Math.max(this._min VirtualStart, val)); 2989 this._virtualStartVal = Math.min(this._maxVirtualStart, Math.max(this._min VirtualStart, val));
2266 this._physicalStart = this._virtualStartVal % this._physicalCount; 2990 if (this._physicalCount === 0) {
2267 this._physicalEnd = (this._physicalStart + this._physicalCount - 1) % this ._physicalCount; 2991 this._physicalStart = 0;
2992 this._physicalEnd = 0;
2993 } else {
2994 this._physicalStart = this._virtualStartVal % this._physicalCount;
2995 this._physicalEnd = (this._physicalStart + this._physicalCount - 1) % th is._physicalCount;
2996 }
2268 }, 2997 },
2269 2998
2270 /** 2999 /**
2271 * Gets the n-th item rendered in `_physicalStart` 3000 * Gets the n-th item rendered in `_physicalStart`
2272 */ 3001 */
2273 get _virtualStart() { 3002 get _virtualStart() {
2274 return this._virtualStartVal; 3003 return this._virtualStartVal;
2275 }, 3004 },
2276 3005
2277 /** 3006 /**
2278 * An optimal physical size such that we will have enough physical items 3007 * An optimal physical size such that we will have enough physical items
2279 * to fill up the viewport and recycle when the user scrolls. 3008 * to fill up the viewport and recycle when the user scrolls.
2280 * 3009 *
2281 * This default value assumes that we will at least have the equivalent 3010 * This default value assumes that we will at least have the equivalent
2282 * to a viewport of physical items above and below the user's viewport. 3011 * to a viewport of physical items above and below the user's viewport.
2283 */ 3012 */
2284 get _optPhysicalSize() { 3013 get _optPhysicalSize() {
2285 return this._viewportSize * this._maxPages; 3014 return this._viewportSize * this._maxPages;
2286 }, 3015 },
2287 3016
2288 /** 3017 /**
2289 * True if the current list is visible. 3018 * True if the current list is visible.
2290 */ 3019 */
2291 get _isVisible() { 3020 get _isVisible() {
2292 return this._scroller && Boolean(this._scroller.offsetWidth || this._scrol ler.offsetHeight); 3021 return this.scrollTarget && Boolean(this.scrollTarget.offsetWidth || this. scrollTarget.offsetHeight);
2293 }, 3022 },
2294 3023
2295 /** 3024 /**
2296 * Gets the index of the first visible item in the viewport. 3025 * Gets the index of the first visible item in the viewport.
2297 * 3026 *
2298 * @type {number} 3027 * @type {number}
2299 */ 3028 */
2300 get firstVisibleIndex() { 3029 get firstVisibleIndex() {
2301 var physicalOffset;
2302
2303 if (this._firstVisibleIndexVal === null) { 3030 if (this._firstVisibleIndexVal === null) {
2304 physicalOffset = this._physicalTop; 3031 var physicalOffset = this._physicalTop;
2305 3032
2306 this._firstVisibleIndexVal = this._iterateItems( 3033 this._firstVisibleIndexVal = this._iterateItems(
2307 function(pidx, vidx) { 3034 function(pidx, vidx) {
2308 physicalOffset += this._physicalSizes[pidx]; 3035 physicalOffset += this._physicalSizes[pidx];
2309 3036
2310 if (physicalOffset > this._scrollPosition) { 3037 if (physicalOffset > this._scrollPosition) {
2311 return vidx; 3038 return vidx;
2312 } 3039 }
2313 }) || 0; 3040 }) || 0;
2314 } 3041 }
2315
2316 return this._firstVisibleIndexVal; 3042 return this._firstVisibleIndexVal;
2317 }, 3043 },
2318 3044
3045 /**
3046 * Gets the index of the last visible item in the viewport.
3047 *
3048 * @type {number}
3049 */
3050 get lastVisibleIndex() {
3051 if (this._lastVisibleIndexVal === null) {
3052 var physicalOffset = this._physicalTop;
3053
3054 this._iterateItems(function(pidx, vidx) {
3055 physicalOffset += this._physicalSizes[pidx];
3056
3057 if(physicalOffset <= this._scrollBottom) {
3058 this._lastVisibleIndexVal = vidx;
3059 }
3060 });
3061 }
3062 return this._lastVisibleIndexVal;
3063 },
3064
2319 ready: function() { 3065 ready: function() {
2320 if (IOS_TOUCH_SCROLLING) { 3066 this.addEventListener('focus', this._didFocus.bind(this), true);
2321 this._scrollListener = function() { 3067 },
2322 requestAnimationFrame(this._scrollHandler.bind(this)); 3068
2323 }.bind(this);
2324 } else {
2325 this._scrollListener = this._scrollHandler.bind(this);
2326 }
2327 },
2328
2329 /**
2330 * When the element has been attached to the DOM tree.
2331 */
2332 attached: function() { 3069 attached: function() {
2333 // delegate to the parent's scroller
2334 // e.g. paper-scroll-header-panel
2335 var el = Polymer.dom(this);
2336
2337 var parentNode = /** @type {?{scroller: ?Element}} */ (el.parentNode);
2338 if (parentNode && parentNode.scroller) {
2339 this._scroller = parentNode.scroller;
2340 } else {
2341 this._scroller = this;
2342 this.classList.add('has-scroller');
2343 }
2344
2345 if (IOS_TOUCH_SCROLLING) {
2346 this._scroller.style.webkitOverflowScrolling = 'touch';
2347 }
2348
2349 this._scroller.addEventListener('scroll', this._scrollListener);
2350
2351 this.updateViewportBoundaries(); 3070 this.updateViewportBoundaries();
2352 this._render(); 3071 this._render();
2353 }, 3072 },
2354 3073
2355 /**
2356 * When the element has been removed from the DOM tree.
2357 */
2358 detached: function() { 3074 detached: function() {
2359 this._itemsRendered = false; 3075 this._itemsRendered = false;
2360 if (this._scroller) { 3076 },
2361 this._scroller.removeEventListener('scroll', this._scrollListener); 3077
2362 } 3078 get _defaultScrollTarget() {
3079 return this;
3080 },
3081
3082 /**
3083 * Set the overflow property if this element has its own scrolling region
3084 */
3085 _setOverflow: function(scrollTarget) {
3086 this.style.webkitOverflowScrolling = scrollTarget === this ? 'touch' : '';
3087 this.style.overflow = scrollTarget === this ? 'auto' : '';
2363 }, 3088 },
2364 3089
2365 /** 3090 /**
2366 * Invoke this method if you dynamically update the viewport's 3091 * Invoke this method if you dynamically update the viewport's
2367 * size or CSS padding. 3092 * size or CSS padding.
2368 * 3093 *
2369 * @method updateViewportBoundaries 3094 * @method updateViewportBoundaries
2370 */ 3095 */
2371 updateViewportBoundaries: function() { 3096 updateViewportBoundaries: function() {
2372 var scrollerStyle = window.getComputedStyle(this._scroller); 3097 var scrollerStyle = window.getComputedStyle(this.scrollTarget);
2373 this._scrollerPaddingTop = parseInt(scrollerStyle['padding-top'], 10); 3098 this._scrollerPaddingTop = parseInt(scrollerStyle['padding-top'], 10);
2374 this._viewportSize = this._scroller.offsetHeight; 3099 this._viewportSize = this._scrollTargetHeight;
2375 }, 3100 },
2376 3101
2377 /** 3102 /**
2378 * Update the models, the position of the 3103 * Update the models, the position of the
2379 * items in the viewport and recycle tiles as needed. 3104 * items in the viewport and recycle tiles as needed.
2380 */ 3105 */
2381 _refresh: function() { 3106 _scrollHandler: function() {
2382 // clamp the `scrollTop` value 3107 // clamp the `scrollTop` value
2383 // IE 10|11 scrollTop may go above `_maxScrollTop` 3108 // IE 10|11 scrollTop may go above `_maxScrollTop`
2384 // iOS `scrollTop` may go below 0 and above `_maxScrollTop` 3109 // iOS `scrollTop` may go below 0 and above `_maxScrollTop`
2385 var scrollTop = Math.max(0, Math.min(this._maxScrollTop, this._scroller.sc rollTop)); 3110 var scrollTop = Math.max(0, Math.min(this._maxScrollTop, this._scrollTop)) ;
2386 var tileHeight, tileTop, kth, recycledTileSet, scrollBottom, physicalBotto m; 3111 var tileHeight, tileTop, kth, recycledTileSet, scrollBottom, physicalBotto m;
2387 var ratio = this._ratio; 3112 var ratio = this._ratio;
2388 var delta = scrollTop - this._scrollPosition; 3113 var delta = scrollTop - this._scrollPosition;
2389 var recycledTiles = 0; 3114 var recycledTiles = 0;
2390 var hiddenContentSize = this._hiddenContentSize; 3115 var hiddenContentSize = this._hiddenContentSize;
2391 var currentRatio = ratio; 3116 var currentRatio = ratio;
2392 var movingUp = []; 3117 var movingUp = [];
2393 3118
2394 // track the last `scrollTop` 3119 // track the last `scrollTop`
2395 this._scrollPosition = scrollTop; 3120 this._scrollPosition = scrollTop;
2396 3121
2397 // clear cached visible index 3122 // clear cached visible index
2398 this._firstVisibleIndexVal = null; 3123 this._firstVisibleIndexVal = null;
3124 this._lastVisibleIndexVal = null;
2399 3125
2400 scrollBottom = this._scrollBottom; 3126 scrollBottom = this._scrollBottom;
2401 physicalBottom = this._physicalBottom; 3127 physicalBottom = this._physicalBottom;
2402 3128
2403 // random access 3129 // random access
2404 if (Math.abs(delta) > this._physicalSize) { 3130 if (Math.abs(delta) > this._physicalSize) {
2405 this._physicalTop += delta; 3131 this._physicalTop += delta;
2406 recycledTiles = Math.round(delta / this._physicalAverage); 3132 recycledTiles = Math.round(delta / this._physicalAverage);
2407 } 3133 }
2408 // scroll up 3134 // scroll up
(...skipping 69 matching lines...) Expand 10 before | Expand all | Expand 10 after
2478 if (physicalBottom < scrollBottom || this._physicalTop > scrollTop) { 3204 if (physicalBottom < scrollBottom || this._physicalTop > scrollTop) {
2479 this.async(this._increasePool.bind(this, 1)); 3205 this.async(this._increasePool.bind(this, 1));
2480 } 3206 }
2481 } else { 3207 } else {
2482 this._virtualStart = this._virtualStart + recycledTiles; 3208 this._virtualStart = this._virtualStart + recycledTiles;
2483 this._update(recycledTileSet, movingUp); 3209 this._update(recycledTileSet, movingUp);
2484 } 3210 }
2485 }, 3211 },
2486 3212
2487 /** 3213 /**
2488 * Update the list of items, starting from the `_virtualStartVal` item. 3214 * Update the list of items, starting from the `_virtualStart` item.
2489 * @param {!Array<number>=} itemSet 3215 * @param {!Array<number>=} itemSet
2490 * @param {!Array<number>=} movingUp 3216 * @param {!Array<number>=} movingUp
2491 */ 3217 */
2492 _update: function(itemSet, movingUp) { 3218 _update: function(itemSet, movingUp) {
3219 // manage focus
3220 if (this._isIndexRendered(this._focusedIndex)) {
3221 this._restoreFocusedItem();
3222 } else {
3223 this._createFocusBackfillItem();
3224 }
2493 // update models 3225 // update models
2494 this._assignModels(itemSet); 3226 this._assignModels(itemSet);
2495
2496 // measure heights 3227 // measure heights
2497 this._updateMetrics(itemSet); 3228 this._updateMetrics(itemSet);
2498
2499 // adjust offset after measuring 3229 // adjust offset after measuring
2500 if (movingUp) { 3230 if (movingUp) {
2501 while (movingUp.length) { 3231 while (movingUp.length) {
2502 this._physicalTop -= this._physicalSizes[movingUp.pop()]; 3232 this._physicalTop -= this._physicalSizes[movingUp.pop()];
2503 } 3233 }
2504 } 3234 }
2505 // update the position of the items 3235 // update the position of the items
2506 this._positionItems(); 3236 this._positionItems();
2507
2508 // set the scroller size 3237 // set the scroller size
2509 this._updateScrollerSize(); 3238 this._updateScrollerSize();
2510
2511 // increase the pool of physical items 3239 // increase the pool of physical items
2512 this._increasePoolIfNeeded(); 3240 this._increasePoolIfNeeded();
2513 }, 3241 },
2514 3242
2515 /** 3243 /**
2516 * Creates a pool of DOM elements and attaches them to the local dom. 3244 * Creates a pool of DOM elements and attaches them to the local dom.
2517 */ 3245 */
2518 _createPool: function(size) { 3246 _createPool: function(size) {
2519 var physicalItems = new Array(size); 3247 var physicalItems = new Array(size);
2520 3248
2521 this._ensureTemplatized(); 3249 this._ensureTemplatized();
2522 3250
2523 for (var i = 0; i < size; i++) { 3251 for (var i = 0; i < size; i++) {
2524 var inst = this.stamp(null); 3252 var inst = this.stamp(null);
2525 // First element child is item; Safari doesn't support children[0] 3253 // First element child is item; Safari doesn't support children[0]
2526 // on a doc fragment 3254 // on a doc fragment
2527 physicalItems[i] = inst.root.querySelector('*'); 3255 physicalItems[i] = inst.root.querySelector('*');
2528 Polymer.dom(this).appendChild(inst.root); 3256 Polymer.dom(this).appendChild(inst.root);
2529 } 3257 }
2530
2531 return physicalItems; 3258 return physicalItems;
2532 }, 3259 },
2533 3260
2534 /** 3261 /**
2535 * Increases the pool of physical items only if needed. 3262 * Increases the pool of physical items only if needed.
2536 * This function will allocate additional physical items 3263 * This function will allocate additional physical items
2537 * if the physical size is shorter than `_optPhysicalSize` 3264 * if the physical size is shorter than `_optPhysicalSize`
2538 */ 3265 */
2539 _increasePoolIfNeeded: function() { 3266 _increasePoolIfNeeded: function() {
2540 if (this._viewportSize !== 0 && this._physicalSize < this._optPhysicalSize ) { 3267 if (this._viewportSize === 0 || this._physicalSize >= this._optPhysicalSiz e) {
2541 // 0 <= `currentPage` <= `_maxPages` 3268 return false;
2542 var currentPage = Math.floor(this._physicalSize / this._viewportSize); 3269 }
2543 3270 // 0 <= `currentPage` <= `_maxPages`
2544 if (currentPage === 0) { 3271 var currentPage = Math.floor(this._physicalSize / this._viewportSize);
2545 // fill the first page 3272 if (currentPage === 0) {
2546 this.async(this._increasePool.bind(this, Math.round(this._physicalCoun t * 0.5))); 3273 // fill the first page
2547 } else if (this._lastPage !== currentPage) { 3274 this._debounceTemplate(this._increasePool.bind(this, Math.round(this._ph ysicalCount * 0.5)));
2548 // once a page is filled up, paint it and defer the next increase 3275 } else if (this._lastPage !== currentPage) {
2549 requestAnimationFrame(this._increasePool.bind(this, 1)); 3276 // paint the page and defer the next increase
2550 } else { 3277 // wait 16ms which is rough enough to get paint cycle.
2551 // fill the rest of the pages 3278 Polymer.dom.addDebouncer(this.debounce('_debounceTemplate', this._increa sePool.bind(this, 1), 16));
2552 this.async(this._increasePool.bind(this, 1)); 3279 } else {
2553 } 3280 // fill the rest of the pages
2554 this._lastPage = currentPage; 3281 this._debounceTemplate(this._increasePool.bind(this, 1));
2555 return true; 3282 }
2556 } 3283 this._lastPage = currentPage;
2557 return false; 3284 return true;
2558 }, 3285 },
2559 3286
2560 /** 3287 /**
2561 * Increases the pool size. 3288 * Increases the pool size.
2562 */ 3289 */
2563 _increasePool: function(missingItems) { 3290 _increasePool: function(missingItems) {
2564 // limit the size 3291 // limit the size
2565 var nextPhysicalCount = Math.min( 3292 var nextPhysicalCount = Math.min(
2566 this._physicalCount + missingItems, 3293 this._physicalCount + missingItems,
2567 this._virtualCount, 3294 this._virtualCount - this._virtualStart,
2568 MAX_PHYSICAL_COUNT 3295 MAX_PHYSICAL_COUNT
2569 ); 3296 );
2570 var prevPhysicalCount = this._physicalCount; 3297 var prevPhysicalCount = this._physicalCount;
2571 var delta = nextPhysicalCount - prevPhysicalCount; 3298 var delta = nextPhysicalCount - prevPhysicalCount;
2572 3299
2573 if (delta > 0) { 3300 if (delta > 0) {
2574 [].push.apply(this._physicalItems, this._createPool(delta)); 3301 [].push.apply(this._physicalItems, this._createPool(delta));
2575 [].push.apply(this._physicalSizes, new Array(delta)); 3302 [].push.apply(this._physicalSizes, new Array(delta));
2576 3303
2577 this._physicalCount = prevPhysicalCount + delta; 3304 this._physicalCount = prevPhysicalCount + delta;
2578 // tail call 3305 // tail call
2579 return this._update(); 3306 return this._update();
2580 } 3307 }
2581 }, 3308 },
2582 3309
2583 /** 3310 /**
2584 * Render a new list of items. This method does exactly the same as `update` , 3311 * Render a new list of items. This method does exactly the same as `update` ,
2585 * but it also ensures that only one `update` cycle is created. 3312 * but it also ensures that only one `update` cycle is created.
2586 */ 3313 */
2587 _render: function() { 3314 _render: function() {
2588 var requiresUpdate = this._virtualCount > 0 || this._physicalCount > 0; 3315 var requiresUpdate = this._virtualCount > 0 || this._physicalCount > 0;
2589 3316
2590 if (this.isAttached && !this._itemsRendered && this._isVisible && requires Update) { 3317 if (this.isAttached && !this._itemsRendered && this._isVisible && requires Update) {
2591 this._lastPage = 0; 3318 this._lastPage = 0;
2592 this._update(); 3319 this._update();
3320 this._scrollHandler();
2593 this._itemsRendered = true; 3321 this._itemsRendered = true;
2594 } 3322 }
2595 }, 3323 },
2596 3324
2597 /** 3325 /**
2598 * Templetizes the user template. 3326 * Templetizes the user template.
2599 */ 3327 */
2600 _ensureTemplatized: function() { 3328 _ensureTemplatized: function() {
2601 if (!this.ctor) { 3329 if (!this.ctor) {
2602 // Template instance props that should be excluded from forwarding 3330 // Template instance props that should be excluded from forwarding
2603 var props = {}; 3331 var props = {};
2604
2605 props.__key__ = true; 3332 props.__key__ = true;
2606 props[this.as] = true; 3333 props[this.as] = true;
2607 props[this.indexAs] = true; 3334 props[this.indexAs] = true;
2608 props[this.selectedAs] = true; 3335 props[this.selectedAs] = true;
3336 props.tabIndex = true;
2609 3337
2610 this._instanceProps = props; 3338 this._instanceProps = props;
2611 this._userTemplate = Polymer.dom(this).querySelector('template'); 3339 this._userTemplate = Polymer.dom(this).querySelector('template');
2612 3340
2613 if (this._userTemplate) { 3341 if (this._userTemplate) {
2614 this.templatize(this._userTemplate); 3342 this.templatize(this._userTemplate);
2615 } else { 3343 } else {
2616 console.warn('iron-list requires a template to be provided in light-do m'); 3344 console.warn('iron-list requires a template to be provided in light-do m');
2617 } 3345 }
2618 } 3346 }
(...skipping 47 matching lines...) Expand 10 before | Expand all | Expand 10 after
2666 /** 3394 /**
2667 * Called as a side effect of a host items.<key>.<path> path change, 3395 * Called as a side effect of a host items.<key>.<path> path change,
2668 * responsible for notifying item.<path> changes to row for key. 3396 * responsible for notifying item.<path> changes to row for key.
2669 */ 3397 */
2670 _forwardItemPath: function(path, value) { 3398 _forwardItemPath: function(path, value) {
2671 if (this._physicalIndexForKey) { 3399 if (this._physicalIndexForKey) {
2672 var dot = path.indexOf('.'); 3400 var dot = path.indexOf('.');
2673 var key = path.substring(0, dot < 0 ? path.length : dot); 3401 var key = path.substring(0, dot < 0 ? path.length : dot);
2674 var idx = this._physicalIndexForKey[key]; 3402 var idx = this._physicalIndexForKey[key];
2675 var row = this._physicalItems[idx]; 3403 var row = this._physicalItems[idx];
3404
3405 if (idx === this._focusedIndex && this._offscreenFocusedItem) {
3406 row = this._offscreenFocusedItem;
3407 }
2676 if (row) { 3408 if (row) {
2677 var inst = row._templateInstance; 3409 var inst = row._templateInstance;
2678 if (dot >= 0) { 3410 if (dot >= 0) {
2679 path = this.as + '.' + path.substring(dot+1); 3411 path = this.as + '.' + path.substring(dot+1);
2680 inst.notifyPath(path, value, true); 3412 inst.notifyPath(path, value, true);
2681 } else { 3413 } else {
2682 inst[this.as] = value; 3414 inst[this.as] = value;
2683 } 3415 }
2684 } 3416 }
2685 } 3417 }
2686 }, 3418 },
2687 3419
2688 /** 3420 /**
2689 * Called when the items have changed. That is, ressignments 3421 * Called when the items have changed. That is, ressignments
2690 * to `items`, splices or updates to a single item. 3422 * to `items`, splices or updates to a single item.
2691 */ 3423 */
2692 _itemsChanged: function(change) { 3424 _itemsChanged: function(change) {
2693 if (change.path === 'items') { 3425 if (change.path === 'items') {
3426
3427 this._restoreFocusedItem();
2694 // render the new set 3428 // render the new set
2695 this._itemsRendered = false; 3429 this._itemsRendered = false;
2696
2697 // update the whole set 3430 // update the whole set
2698 this._virtualStartVal = 0; 3431 this._virtualStart = 0;
2699 this._physicalTop = 0; 3432 this._physicalTop = 0;
2700 this._virtualCount = this.items ? this.items.length : 0; 3433 this._virtualCount = this.items ? this.items.length : 0;
3434 this._focusedIndex = 0;
2701 this._collection = this.items ? Polymer.Collection.get(this.items) : nul l; 3435 this._collection = this.items ? Polymer.Collection.get(this.items) : nul l;
2702 this._physicalIndexForKey = {}; 3436 this._physicalIndexForKey = {};
2703 3437
2704 // scroll to the top
2705 this._resetScrollPosition(0); 3438 this._resetScrollPosition(0);
2706 3439
2707 // create the initial physical items 3440 // create the initial physical items
2708 if (!this._physicalItems) { 3441 if (!this._physicalItems) {
2709 this._physicalCount = Math.max(1, Math.min(DEFAULT_PHYSICAL_COUNT, thi s._virtualCount)); 3442 this._physicalCount = Math.max(1, Math.min(DEFAULT_PHYSICAL_COUNT, thi s._virtualCount));
2710 this._physicalItems = this._createPool(this._physicalCount); 3443 this._physicalItems = this._createPool(this._physicalCount);
2711 this._physicalSizes = new Array(this._physicalCount); 3444 this._physicalSizes = new Array(this._physicalCount);
2712 } 3445 }
2713 3446 this._debounceTemplate(this._render);
2714 this.debounce('refresh', this._render);
2715 3447
2716 } else if (change.path === 'items.splices') { 3448 } else if (change.path === 'items.splices') {
2717 // render the new set 3449 // render the new set
2718 this._itemsRendered = false; 3450 this._itemsRendered = false;
2719
2720 this._adjustVirtualIndex(change.value.indexSplices); 3451 this._adjustVirtualIndex(change.value.indexSplices);
2721 this._virtualCount = this.items ? this.items.length : 0; 3452 this._virtualCount = this.items ? this.items.length : 0;
2722 3453
2723 this.debounce('refresh', this._render); 3454 this._debounceTemplate(this._render);
3455
3456 if (this._focusedIndex < 0 || this._focusedIndex >= this._virtualCount) {
3457 this._focusedIndex = 0;
3458 }
3459 this._debounceTemplate(this._render);
2724 3460
2725 } else { 3461 } else {
2726 // update a single item 3462 // update a single item
2727 this._forwardItemPath(change.path.split('.').slice(1).join('.'), change. value); 3463 this._forwardItemPath(change.path.split('.').slice(1).join('.'), change. value);
2728 } 3464 }
2729 }, 3465 },
2730 3466
2731 /** 3467 /**
2732 * @param {!Array<!PolymerSplice>} splices 3468 * @param {!Array<!PolymerSplice>} splices
2733 */ 3469 */
2734 _adjustVirtualIndex: function(splices) { 3470 _adjustVirtualIndex: function(splices) {
2735 var i, splice, idx; 3471 var i, splice, idx;
2736 3472
2737 for (i = 0; i < splices.length; i++) { 3473 for (i = 0; i < splices.length; i++) {
2738 splice = splices[i]; 3474 splice = splices[i];
2739 3475
2740 // deselect removed items 3476 // deselect removed items
2741 splice.removed.forEach(this.$.selector.deselect, this.$.selector); 3477 splice.removed.forEach(this.$.selector.deselect, this.$.selector);
2742 3478
2743 idx = splice.index; 3479 idx = splice.index;
2744 // We only need to care about changes happening above the current positi on 3480 // We only need to care about changes happening above the current positi on
2745 if (idx >= this._virtualStartVal) { 3481 if (idx >= this._virtualStart) {
2746 break; 3482 break;
2747 } 3483 }
2748 3484
2749 this._virtualStart = this._virtualStart + 3485 this._virtualStart = this._virtualStart +
2750 Math.max(splice.addedCount - splice.removed.length, idx - this._virt ualStartVal); 3486 Math.max(splice.addedCount - splice.removed.length, idx - this._virt ualStart);
2751 } 3487 }
2752 },
2753
2754 _scrollHandler: function() {
2755 this._refresh();
2756 }, 3488 },
2757 3489
2758 /** 3490 /**
2759 * Executes a provided function per every physical index in `itemSet` 3491 * Executes a provided function per every physical index in `itemSet`
2760 * `itemSet` default value is equivalent to the entire set of physical index es. 3492 * `itemSet` default value is equivalent to the entire set of physical index es.
2761 * 3493 *
2762 * @param {!function(number, number)} fn 3494 * @param {!function(number, number)} fn
2763 * @param {!Array<number>=} itemSet 3495 * @param {!Array<number>=} itemSet
2764 */ 3496 */
2765 _iterateItems: function(fn, itemSet) { 3497 _iterateItems: function(fn, itemSet) {
2766 var pidx, vidx, rtn, i; 3498 var pidx, vidx, rtn, i;
2767 3499
2768 if (arguments.length === 2 && itemSet) { 3500 if (arguments.length === 2 && itemSet) {
2769 for (i = 0; i < itemSet.length; i++) { 3501 for (i = 0; i < itemSet.length; i++) {
2770 pidx = itemSet[i]; 3502 pidx = itemSet[i];
2771 if (pidx >= this._physicalStart) { 3503 if (pidx >= this._physicalStart) {
2772 vidx = this._virtualStartVal + (pidx - this._physicalStart); 3504 vidx = this._virtualStart + (pidx - this._physicalStart);
2773 } else { 3505 } else {
2774 vidx = this._virtualStartVal + (this._physicalCount - this._physical Start) + pidx; 3506 vidx = this._virtualStart + (this._physicalCount - this._physicalSta rt) + pidx;
2775 } 3507 }
2776 if ((rtn = fn.call(this, pidx, vidx)) != null) { 3508 if ((rtn = fn.call(this, pidx, vidx)) != null) {
2777 return rtn; 3509 return rtn;
2778 } 3510 }
2779 } 3511 }
2780 } else { 3512 } else {
2781 pidx = this._physicalStart; 3513 pidx = this._physicalStart;
2782 vidx = this._virtualStartVal; 3514 vidx = this._virtualStart;
2783 3515
2784 for (; pidx < this._physicalCount; pidx++, vidx++) { 3516 for (; pidx < this._physicalCount; pidx++, vidx++) {
2785 if ((rtn = fn.call(this, pidx, vidx)) != null) { 3517 if ((rtn = fn.call(this, pidx, vidx)) != null) {
2786 return rtn; 3518 return rtn;
2787 } 3519 }
2788 } 3520 }
2789 3521 for (pidx = 0; pidx < this._physicalStart; pidx++, vidx++) {
2790 pidx = 0;
2791
2792 for (; pidx < this._physicalStart; pidx++, vidx++) {
2793 if ((rtn = fn.call(this, pidx, vidx)) != null) { 3522 if ((rtn = fn.call(this, pidx, vidx)) != null) {
2794 return rtn; 3523 return rtn;
2795 } 3524 }
2796 } 3525 }
2797 } 3526 }
2798 }, 3527 },
2799 3528
2800 /** 3529 /**
2801 * Assigns the data models to a given set of items. 3530 * Assigns the data models to a given set of items.
2802 * @param {!Array<number>=} itemSet 3531 * @param {!Array<number>=} itemSet
2803 */ 3532 */
2804 _assignModels: function(itemSet) { 3533 _assignModels: function(itemSet) {
2805 this._iterateItems(function(pidx, vidx) { 3534 this._iterateItems(function(pidx, vidx) {
2806 var el = this._physicalItems[pidx]; 3535 var el = this._physicalItems[pidx];
2807 var inst = el._templateInstance; 3536 var inst = el._templateInstance;
2808 var item = this.items && this.items[vidx]; 3537 var item = this.items && this.items[vidx];
2809 3538
2810 if (item) { 3539 if (item !== undefined && item !== null) {
2811 inst[this.as] = item; 3540 inst[this.as] = item;
2812 inst.__key__ = this._collection.getKey(item); 3541 inst.__key__ = this._collection.getKey(item);
2813 inst[this.selectedAs] = 3542 inst[this.selectedAs] = /** @type {!ArraySelectorElement} */ (this.$.s elector).isSelected(item);
2814 /** @type {!ArraySelectorElement} */ (this.$.selector).isSelected(it em);
2815 inst[this.indexAs] = vidx; 3543 inst[this.indexAs] = vidx;
3544 inst.tabIndex = vidx === this._focusedIndex ? 0 : -1;
2816 el.removeAttribute('hidden'); 3545 el.removeAttribute('hidden');
2817 this._physicalIndexForKey[inst.__key__] = pidx; 3546 this._physicalIndexForKey[inst.__key__] = pidx;
2818 } else { 3547 } else {
2819 inst.__key__ = null; 3548 inst.__key__ = null;
2820 el.setAttribute('hidden', ''); 3549 el.setAttribute('hidden', '');
2821 } 3550 }
2822 3551
2823 }, itemSet); 3552 }, itemSet);
2824 }, 3553 },
2825 3554
2826 /** 3555 /**
2827 * Updates the height for a given set of items. 3556 * Updates the height for a given set of items.
2828 * 3557 *
2829 * @param {!Array<number>=} itemSet 3558 * @param {!Array<number>=} itemSet
2830 */ 3559 */
2831 _updateMetrics: function(itemSet) { 3560 _updateMetrics: function(itemSet) {
3561 // Make sure we distributed all the physical items
3562 // so we can measure them
3563 Polymer.dom.flush();
3564
2832 var newPhysicalSize = 0; 3565 var newPhysicalSize = 0;
2833 var oldPhysicalSize = 0; 3566 var oldPhysicalSize = 0;
2834 var prevAvgCount = this._physicalAverageCount; 3567 var prevAvgCount = this._physicalAverageCount;
2835 var prevPhysicalAvg = this._physicalAverage; 3568 var prevPhysicalAvg = this._physicalAverage;
2836 // Make sure we distributed all the physical items
2837 // so we can measure them
2838 Polymer.dom.flush();
2839 3569
2840 this._iterateItems(function(pidx, vidx) { 3570 this._iterateItems(function(pidx, vidx) {
3571
2841 oldPhysicalSize += this._physicalSizes[pidx] || 0; 3572 oldPhysicalSize += this._physicalSizes[pidx] || 0;
2842 this._physicalSizes[pidx] = this._physicalItems[pidx].offsetHeight; 3573 this._physicalSizes[pidx] = this._physicalItems[pidx].offsetHeight;
2843 newPhysicalSize += this._physicalSizes[pidx]; 3574 newPhysicalSize += this._physicalSizes[pidx];
2844 this._physicalAverageCount += this._physicalSizes[pidx] ? 1 : 0; 3575 this._physicalAverageCount += this._physicalSizes[pidx] ? 1 : 0;
3576
2845 }, itemSet); 3577 }, itemSet);
2846 3578
2847 this._physicalSize = this._physicalSize + newPhysicalSize - oldPhysicalSiz e; 3579 this._physicalSize = this._physicalSize + newPhysicalSize - oldPhysicalSiz e;
2848 this._viewportSize = this._scroller.offsetHeight; 3580 this._viewportSize = this._scrollTargetHeight;
2849 3581
2850 // update the average if we measured something 3582 // update the average if we measured something
2851 if (this._physicalAverageCount !== prevAvgCount) { 3583 if (this._physicalAverageCount !== prevAvgCount) {
2852 this._physicalAverage = Math.round( 3584 this._physicalAverage = Math.round(
2853 ((prevPhysicalAvg * prevAvgCount) + newPhysicalSize) / 3585 ((prevPhysicalAvg * prevAvgCount) + newPhysicalSize) /
2854 this._physicalAverageCount); 3586 this._physicalAverageCount);
2855 } 3587 }
2856 }, 3588 },
2857 3589
2858 /** 3590 /**
2859 * Updates the position of the physical items. 3591 * Updates the position of the physical items.
2860 */ 3592 */
2861 _positionItems: function() { 3593 _positionItems: function() {
2862 this._adjustScrollPosition(); 3594 this._adjustScrollPosition();
2863 3595
2864 var y = this._physicalTop; 3596 var y = this._physicalTop;
2865 3597
2866 this._iterateItems(function(pidx) { 3598 this._iterateItems(function(pidx) {
2867 3599
2868 this.transform('translate3d(0, ' + y + 'px, 0)', this._physicalItems[pid x]); 3600 this.translate3d(0, y + 'px', 0, this._physicalItems[pidx]);
2869 y += this._physicalSizes[pidx]; 3601 y += this._physicalSizes[pidx];
2870 3602
2871 }); 3603 });
2872 }, 3604 },
2873 3605
2874 /** 3606 /**
2875 * Adjusts the scroll position when it was overestimated. 3607 * Adjusts the scroll position when it was overestimated.
2876 */ 3608 */
2877 _adjustScrollPosition: function() { 3609 _adjustScrollPosition: function() {
2878 var deltaHeight = this._virtualStartVal === 0 ? this._physicalTop : 3610 var deltaHeight = this._virtualStart === 0 ? this._physicalTop :
2879 Math.min(this._scrollPosition + this._physicalTop, 0); 3611 Math.min(this._scrollPosition + this._physicalTop, 0);
2880 3612
2881 if (deltaHeight) { 3613 if (deltaHeight) {
2882 this._physicalTop = this._physicalTop - deltaHeight; 3614 this._physicalTop = this._physicalTop - deltaHeight;
2883
2884 // juking scroll position during interial scrolling on iOS is no bueno 3615 // juking scroll position during interial scrolling on iOS is no bueno
2885 if (!IOS_TOUCH_SCROLLING) { 3616 if (!IOS_TOUCH_SCROLLING) {
2886 this._resetScrollPosition(this._scroller.scrollTop - deltaHeight); 3617 this._resetScrollPosition(this._scrollTop - deltaHeight);
2887 } 3618 }
2888 } 3619 }
2889 }, 3620 },
2890 3621
2891 /** 3622 /**
2892 * Sets the position of the scroll. 3623 * Sets the position of the scroll.
2893 */ 3624 */
2894 _resetScrollPosition: function(pos) { 3625 _resetScrollPosition: function(pos) {
2895 if (this._scroller) { 3626 if (this.scrollTarget) {
2896 this._scroller.scrollTop = pos; 3627 this._scrollTop = pos;
2897 this._scrollPosition = this._scroller.scrollTop; 3628 this._scrollPosition = this._scrollTop;
2898 } 3629 }
2899 }, 3630 },
2900 3631
2901 /** 3632 /**
2902 * Sets the scroll height, that's the height of the content, 3633 * Sets the scroll height, that's the height of the content,
2903 * 3634 *
2904 * @param {boolean=} forceUpdate If true, updates the height no matter what. 3635 * @param {boolean=} forceUpdate If true, updates the height no matter what.
2905 */ 3636 */
2906 _updateScrollerSize: function(forceUpdate) { 3637 _updateScrollerSize: function(forceUpdate) {
2907 this._estScrollHeight = (this._physicalBottom + 3638 this._estScrollHeight = (this._physicalBottom +
2908 Math.max(this._virtualCount - this._physicalCount - this._virtualStart Val, 0) * this._physicalAverage); 3639 Math.max(this._virtualCount - this._physicalCount - this._virtualStart , 0) * this._physicalAverage);
2909 3640
2910 forceUpdate = forceUpdate || this._scrollHeight === 0; 3641 forceUpdate = forceUpdate || this._scrollHeight === 0;
2911 forceUpdate = forceUpdate || this._scrollPosition >= this._estScrollHeight - this._physicalSize; 3642 forceUpdate = forceUpdate || this._scrollPosition >= this._estScrollHeight - this._physicalSize;
2912 3643
2913 // amortize height adjustment, so it won't trigger repaints very often 3644 // amortize height adjustment, so it won't trigger repaints very often
2914 if (forceUpdate || Math.abs(this._estScrollHeight - this._scrollHeight) >= this._optPhysicalSize) { 3645 if (forceUpdate || Math.abs(this._estScrollHeight - this._scrollHeight) >= this._optPhysicalSize) {
2915 this.$.items.style.height = this._estScrollHeight + 'px'; 3646 this.$.items.style.height = this._estScrollHeight + 'px';
2916 this._scrollHeight = this._estScrollHeight; 3647 this._scrollHeight = this._estScrollHeight;
2917 } 3648 }
2918 }, 3649 },
2919 3650
2920 /** 3651 /**
2921 * Scroll to a specific item in the virtual list regardless 3652 * Scroll to a specific item in the virtual list regardless
2922 * of the physical items in the DOM tree. 3653 * of the physical items in the DOM tree.
2923 * 3654 *
2924 * @method scrollToIndex 3655 * @method scrollToIndex
2925 * @param {number} idx The index of the item 3656 * @param {number} idx The index of the item
2926 */ 3657 */
2927 scrollToIndex: function(idx) { 3658 scrollToIndex: function(idx) {
2928 if (typeof idx !== 'number') { 3659 if (typeof idx !== 'number') {
2929 return; 3660 return;
2930 } 3661 }
2931 3662
3663 Polymer.dom.flush();
3664
2932 var firstVisible = this.firstVisibleIndex; 3665 var firstVisible = this.firstVisibleIndex;
2933
2934 idx = Math.min(Math.max(idx, 0), this._virtualCount-1); 3666 idx = Math.min(Math.max(idx, 0), this._virtualCount-1);
2935 3667
2936 // start at the previous virtual item 3668 // start at the previous virtual item
2937 // so we have a item above the first visible item 3669 // so we have a item above the first visible item
2938 this._virtualStart = idx - 1; 3670 this._virtualStart = idx - 1;
2939
2940 // assign new models 3671 // assign new models
2941 this._assignModels(); 3672 this._assignModels();
2942
2943 // measure the new sizes 3673 // measure the new sizes
2944 this._updateMetrics(); 3674 this._updateMetrics();
2945
2946 // estimate new physical offset 3675 // estimate new physical offset
2947 this._physicalTop = this._virtualStart * this._physicalAverage; 3676 this._physicalTop = this._virtualStart * this._physicalAverage;
2948 3677
2949 var currentTopItem = this._physicalStart; 3678 var currentTopItem = this._physicalStart;
2950 var currentVirtualItem = this._virtualStart; 3679 var currentVirtualItem = this._virtualStart;
2951 var targetOffsetTop = 0; 3680 var targetOffsetTop = 0;
2952 var hiddenContentSize = this._hiddenContentSize; 3681 var hiddenContentSize = this._hiddenContentSize;
2953 3682
2954 // scroll to the item as much as we can 3683 // scroll to the item as much as we can
2955 while (currentVirtualItem < idx && targetOffsetTop < hiddenContentSize) { 3684 while (currentVirtualItem < idx && targetOffsetTop < hiddenContentSize) {
2956 targetOffsetTop = targetOffsetTop + this._physicalSizes[currentTopItem]; 3685 targetOffsetTop = targetOffsetTop + this._physicalSizes[currentTopItem];
2957 currentTopItem = (currentTopItem + 1) % this._physicalCount; 3686 currentTopItem = (currentTopItem + 1) % this._physicalCount;
2958 currentVirtualItem++; 3687 currentVirtualItem++;
2959 } 3688 }
2960
2961 // update the scroller size 3689 // update the scroller size
2962 this._updateScrollerSize(true); 3690 this._updateScrollerSize(true);
2963
2964 // update the position of the items 3691 // update the position of the items
2965 this._positionItems(); 3692 this._positionItems();
2966
2967 // set the new scroll position 3693 // set the new scroll position
2968 this._resetScrollPosition(this._physicalTop + targetOffsetTop + 1); 3694 this._resetScrollPosition(this._physicalTop + this._scrollerPaddingTop + t argetOffsetTop + 1);
2969
2970 // increase the pool of physical items if needed 3695 // increase the pool of physical items if needed
2971 this._increasePoolIfNeeded(); 3696 this._increasePoolIfNeeded();
2972
2973 // clear cached visible index 3697 // clear cached visible index
2974 this._firstVisibleIndexVal = null; 3698 this._firstVisibleIndexVal = null;
3699 this._lastVisibleIndexVal = null;
2975 }, 3700 },
2976 3701
2977 /** 3702 /**
2978 * Reset the physical average and the average count. 3703 * Reset the physical average and the average count.
2979 */ 3704 */
2980 _resetAverage: function() { 3705 _resetAverage: function() {
2981 this._physicalAverage = 0; 3706 this._physicalAverage = 0;
2982 this._physicalAverageCount = 0; 3707 this._physicalAverageCount = 0;
2983 }, 3708 },
2984 3709
2985 /** 3710 /**
2986 * A handler for the `iron-resize` event triggered by `IronResizableBehavior ` 3711 * A handler for the `iron-resize` event triggered by `IronResizableBehavior `
2987 * when the element is resized. 3712 * when the element is resized.
2988 */ 3713 */
2989 _resizeHandler: function() { 3714 _resizeHandler: function() {
2990 this.debounce('resize', function() { 3715 // iOS fires the resize event when the address bar slides up
3716 if (IOS && Math.abs(this._viewportSize - this._scrollTargetHeight) < 100) {
3717 return;
3718 }
3719 this._debounceTemplate(function() {
2991 this._render(); 3720 this._render();
2992 if (this._itemsRendered && this._physicalItems && this._isVisible) { 3721 if (this._itemsRendered && this._physicalItems && this._isVisible) {
2993 this._resetAverage(); 3722 this._resetAverage();
2994 this.updateViewportBoundaries(); 3723 this.updateViewportBoundaries();
2995 this.scrollToIndex(this.firstVisibleIndex); 3724 this.scrollToIndex(this.firstVisibleIndex);
2996 } 3725 }
2997 }); 3726 });
2998 }, 3727 },
2999 3728
3000 _getModelFromItem: function(item) { 3729 _getModelFromItem: function(item) {
3001 var key = this._collection.getKey(item); 3730 var key = this._collection.getKey(item);
3002 var pidx = this._physicalIndexForKey[key]; 3731 var pidx = this._physicalIndexForKey[key];
3003 3732
3004 if (pidx !== undefined) { 3733 if (pidx !== undefined) {
3005 return this._physicalItems[pidx]._templateInstance; 3734 return this._physicalItems[pidx]._templateInstance;
3006 } 3735 }
3007 return null; 3736 return null;
3008 }, 3737 },
3009 3738
3010 /** 3739 /**
3011 * Gets a valid item instance from its index or the object value. 3740 * Gets a valid item instance from its index or the object value.
3012 * 3741 *
3013 * @param {(Object|number)} item The item object or its index 3742 * @param {(Object|number)} item The item object or its index
3014 */ 3743 */
3015 _getNormalizedItem: function(item) { 3744 _getNormalizedItem: function(item) {
3016 if (typeof item === 'number') { 3745 if (this._collection.getKey(item) === undefined) {
3017 item = this.items[item]; 3746 if (typeof item === 'number') {
3018 if (!item) { 3747 item = this.items[item];
3019 throw new RangeError('<item> not found'); 3748 if (!item) {
3020 } 3749 throw new RangeError('<item> not found');
3021 } else if (this._collection.getKey(item) === undefined) { 3750 }
3751 return item;
3752 }
3022 throw new TypeError('<item> should be a valid item'); 3753 throw new TypeError('<item> should be a valid item');
3023 } 3754 }
3024 return item; 3755 return item;
3025 }, 3756 },
3026 3757
3027 /** 3758 /**
3028 * Select the list item at the given index. 3759 * Select the list item at the given index.
3029 * 3760 *
3030 * @method selectItem 3761 * @method selectItem
3031 * @param {(Object|number)} item The item object or its index 3762 * @param {(Object|number)} item The item object or its index
3032 */ 3763 */
3033 selectItem: function(item) { 3764 selectItem: function(item) {
3034 item = this._getNormalizedItem(item); 3765 item = this._getNormalizedItem(item);
3035 var model = this._getModelFromItem(item); 3766 var model = this._getModelFromItem(item);
3036 3767
3037 if (!this.multiSelection && this.selectedItem) { 3768 if (!this.multiSelection && this.selectedItem) {
3038 this.deselectItem(this.selectedItem); 3769 this.deselectItem(this.selectedItem);
3039 } 3770 }
3040 if (model) { 3771 if (model) {
3041 model[this.selectedAs] = true; 3772 model[this.selectedAs] = true;
3042 } 3773 }
3043 this.$.selector.select(item); 3774 this.$.selector.select(item);
3775 this.updateSizeForItem(item);
3044 }, 3776 },
3045 3777
3046 /** 3778 /**
3047 * Deselects the given item list if it is already selected. 3779 * Deselects the given item list if it is already selected.
3048 * 3780 *
3049 3781
3050 * @method deselect 3782 * @method deselect
3051 * @param {(Object|number)} item The item object or its index 3783 * @param {(Object|number)} item The item object or its index
3052 */ 3784 */
3053 deselectItem: function(item) { 3785 deselectItem: function(item) {
3054 item = this._getNormalizedItem(item); 3786 item = this._getNormalizedItem(item);
3055 var model = this._getModelFromItem(item); 3787 var model = this._getModelFromItem(item);
3056 3788
3057 if (model) { 3789 if (model) {
3058 model[this.selectedAs] = false; 3790 model[this.selectedAs] = false;
3059 } 3791 }
3060 this.$.selector.deselect(item); 3792 this.$.selector.deselect(item);
3793 this.updateSizeForItem(item);
3061 }, 3794 },
3062 3795
3063 /** 3796 /**
3064 * Select or deselect a given item depending on whether the item 3797 * Select or deselect a given item depending on whether the item
3065 * has already been selected. 3798 * has already been selected.
3066 * 3799 *
3067 * @method toggleSelectionForItem 3800 * @method toggleSelectionForItem
3068 * @param {(Object|number)} item The item object or its index 3801 * @param {(Object|number)} item The item object or its index
3069 */ 3802 */
3070 toggleSelectionForItem: function(item) { 3803 toggleSelectionForItem: function(item) {
(...skipping 25 matching lines...) Expand all
3096 } 3829 }
3097 3830
3098 /** @type {!ArraySelectorElement} */ (this.$.selector).clearSelection(); 3831 /** @type {!ArraySelectorElement} */ (this.$.selector).clearSelection();
3099 }, 3832 },
3100 3833
3101 /** 3834 /**
3102 * Add an event listener to `tap` if `selectionEnabled` is true, 3835 * Add an event listener to `tap` if `selectionEnabled` is true,
3103 * it will remove the listener otherwise. 3836 * it will remove the listener otherwise.
3104 */ 3837 */
3105 _selectionEnabledChanged: function(selectionEnabled) { 3838 _selectionEnabledChanged: function(selectionEnabled) {
3106 if (selectionEnabled) { 3839 var handler = selectionEnabled ? this.listen : this.unlisten;
3107 this.listen(this, 'tap', '_selectionHandler'); 3840 handler.call(this, this, 'tap', '_selectionHandler');
3108 this.listen(this, 'keypress', '_selectionHandler');
3109 } else {
3110 this.unlisten(this, 'tap', '_selectionHandler');
3111 this.unlisten(this, 'keypress', '_selectionHandler');
3112 }
3113 }, 3841 },
3114 3842
3115 /** 3843 /**
3116 * Select an item from an event object. 3844 * Select an item from an event object.
3117 */ 3845 */
3118 _selectionHandler: function(e) { 3846 _selectionHandler: function(e) {
3119 if (e.type !== 'keypress' || e.keyCode === 13) { 3847 if (this.selectionEnabled) {
3120 var model = this.modelForElement(e.target); 3848 var model = this.modelForElement(e.target);
3121 if (model) { 3849 if (model) {
3122 this.toggleSelectionForItem(model[this.as]); 3850 this.toggleSelectionForItem(model[this.as]);
3123 } 3851 }
3124 } 3852 }
3125 }, 3853 },
3126 3854
3127 _multiSelectionChanged: function(multiSelection) { 3855 _multiSelectionChanged: function(multiSelection) {
3128 this.clearSelection(); 3856 this.clearSelection();
3129 this.$.selector.multi = multiSelection; 3857 this.$.selector.multi = multiSelection;
3130 }, 3858 },
3131 3859
3132 /** 3860 /**
3133 * Updates the size of an item. 3861 * Updates the size of an item.
3134 * 3862 *
3135 * @method updateSizeForItem 3863 * @method updateSizeForItem
3136 * @param {(Object|number)} item The item object or its index 3864 * @param {(Object|number)} item The item object or its index
3137 */ 3865 */
3138 updateSizeForItem: function(item) { 3866 updateSizeForItem: function(item) {
3139 item = this._getNormalizedItem(item); 3867 item = this._getNormalizedItem(item);
3140 var key = this._collection.getKey(item); 3868 var key = this._collection.getKey(item);
3141 var pidx = this._physicalIndexForKey[key]; 3869 var pidx = this._physicalIndexForKey[key];
3142 3870
3143 if (pidx !== undefined) { 3871 if (pidx !== undefined) {
3144 this._updateMetrics([pidx]); 3872 this._updateMetrics([pidx]);
3145 this._positionItems(); 3873 this._positionItems();
3146 } 3874 }
3875 },
3876
3877 _isIndexRendered: function(idx) {
3878 return idx >= this._virtualStart && idx <= this._virtualEnd;
3879 },
3880
3881 _getPhysicalItemForIndex: function(idx, force) {
3882 if (!this._collection) {
3883 return null;
3884 }
3885 if (!this._isIndexRendered(idx)) {
3886 if (force) {
3887 this.scrollToIndex(idx);
3888 return this._getPhysicalItemForIndex(idx, false);
3889 }
3890 return null;
3891 }
3892 var item = this._getNormalizedItem(idx);
3893 var physicalItem = this._physicalItems[this._physicalIndexForKey[this._col lection.getKey(item)]];
3894
3895 return physicalItem || null;
3896 },
3897
3898 _focusPhysicalItem: function(idx) {
3899 this._restoreFocusedItem();
3900
3901 var physicalItem = this._getPhysicalItemForIndex(idx, true);
3902 if (!physicalItem) {
3903 return;
3904 }
3905 var SECRET = ~(Math.random() * 100);
3906 var model = physicalItem._templateInstance;
3907 var focusable;
3908
3909 model.tabIndex = SECRET;
3910 // the focusable element could be the entire physical item
3911 if (physicalItem.tabIndex === SECRET) {
3912 focusable = physicalItem;
3913 }
3914 // the focusable element could be somewhere within the physical item
3915 if (!focusable) {
3916 focusable = Polymer.dom(physicalItem).querySelector('[tabindex="' + SECR ET + '"]');
3917 }
3918 // restore the tab index
3919 model.tabIndex = 0;
3920 focusable && focusable.focus();
3921 },
3922
3923 _restoreFocusedItem: function() {
3924 if (!this._offscreenFocusedItem) {
3925 return;
3926 }
3927 var item = this._getNormalizedItem(this._focusedIndex);
3928 var pidx = this._physicalIndexForKey[this._collection.getKey(item)];
3929
3930 if (pidx !== undefined) {
3931 this.translate3d(0, HIDDEN_Y, 0, this._physicalItems[pidx]);
3932 this._physicalItems[pidx] = this._offscreenFocusedItem;
3933 }
3934 this._offscreenFocusedItem = null;
3935 },
3936
3937 _removeFocusedItem: function() {
3938 if (!this._offscreenFocusedItem) {
3939 return;
3940 }
3941 Polymer.dom(this).removeChild(this._offscreenFocusedItem);
3942 this._offscreenFocusedItem = null;
3943 this._focusBackfillItem = null;
3944 },
3945
3946 _createFocusBackfillItem: function() {
3947 if (this._offscreenFocusedItem) {
3948 return;
3949 }
3950 var item = this._getNormalizedItem(this._focusedIndex);
3951 var pidx = this._physicalIndexForKey[this._collection.getKey(item)];
3952
3953 this._offscreenFocusedItem = this._physicalItems[pidx];
3954 this.translate3d(0, HIDDEN_Y, 0, this._offscreenFocusedItem);
3955
3956 if (!this._focusBackfillItem) {
3957 var stampedTemplate = this.stamp(null);
3958 this._focusBackfillItem = stampedTemplate.root.querySelector('*');
3959 Polymer.dom(this).appendChild(stampedTemplate.root);
3960 }
3961 this._physicalItems[pidx] = this._focusBackfillItem;
3962 },
3963
3964 _didFocus: function(e) {
3965 var targetModel = this.modelForElement(e.target);
3966 var fidx = this._focusedIndex;
3967
3968 if (!targetModel) {
3969 return;
3970 }
3971 this._restoreFocusedItem();
3972
3973 if (this.modelForElement(this._offscreenFocusedItem) === targetModel) {
3974 this.scrollToIndex(fidx);
3975 } else {
3976 // restore tabIndex for the currently focused item
3977 this._getModelFromItem(this._getNormalizedItem(fidx)).tabIndex = -1;
3978 // set the tabIndex for the next focused item
3979 targetModel.tabIndex = 0;
3980 fidx = /** @type {{index: number}} */(targetModel).index;
3981 this._focusedIndex = fidx;
3982 // bring the item into view
3983 if (fidx < this.firstVisibleIndex || fidx > this.lastVisibleIndex) {
3984 this.scrollToIndex(fidx);
3985 } else {
3986 this._update();
3987 }
3988 }
3989 },
3990
3991 _didMoveUp: function() {
3992 this._focusPhysicalItem(Math.max(0, this._focusedIndex - 1));
3993 },
3994
3995 _didMoveDown: function() {
3996 this._focusPhysicalItem(Math.min(this._virtualCount, this._focusedIndex + 1));
3997 },
3998
3999 _didEnter: function(e) {
4000 // focus the currently focused physical item
4001 this._focusPhysicalItem(this._focusedIndex);
4002 // toggle selection
4003 this._selectionHandler(/** @type {{keyboardEvent: Event}} */(e.detail).key boardEvent);
3147 } 4004 }
3148 }); 4005 });
3149 4006
3150 })(); 4007 })();
3151 (function() { 4008 (function() {
3152 4009
3153 // monostate data 4010 // monostate data
3154 var metaDatas = {}; 4011 var metaDatas = {};
3155 var metaArrays = {}; 4012 var metaArrays = {};
3156 var singleton = null; 4013 var singleton = null;
(...skipping 526 matching lines...) Expand 10 before | Expand all | Expand 10 after
3683 // TODO(dfreedm): `pointer-events: none` works around https://crbug.com/ 370136 4540 // TODO(dfreedm): `pointer-events: none` works around https://crbug.com/ 370136
3684 // TODO(sjmiles): inline style may not be ideal, but avoids requiring a shadow-root 4541 // TODO(sjmiles): inline style may not be ideal, but avoids requiring a shadow-root
3685 svg.style.cssText = 'pointer-events: none; display: block; width: 100%; height: 100%;'; 4542 svg.style.cssText = 'pointer-events: none; display: block; width: 100%; height: 100%;';
3686 svg.appendChild(content).removeAttribute('id'); 4543 svg.appendChild(content).removeAttribute('id');
3687 return svg; 4544 return svg;
3688 } 4545 }
3689 return null; 4546 return null;
3690 } 4547 }
3691 4548
3692 }); 4549 });
3693 (function() {
3694 'use strict';
3695
3696 /**
3697 * Chrome uses an older version of DOM Level 3 Keyboard Events
3698 *
3699 * Most keys are labeled as text, but some are Unicode codepoints.
3700 * Values taken from: http://www.w3.org/TR/2007/WD-DOM-Level-3-Events-200712 21/keyset.html#KeySet-Set
3701 */
3702 var KEY_IDENTIFIER = {
3703 'U+0008': 'backspace',
3704 'U+0009': 'tab',
3705 'U+001B': 'esc',
3706 'U+0020': 'space',
3707 'U+007F': 'del'
3708 };
3709
3710 /**
3711 * Special table for KeyboardEvent.keyCode.
3712 * KeyboardEvent.keyIdentifier is better, and KeyBoardEvent.key is even bett er
3713 * than that.
3714 *
3715 * Values from: https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEve nt.keyCode#Value_of_keyCode
3716 */
3717 var KEY_CODE = {
3718 8: 'backspace',
3719 9: 'tab',
3720 13: 'enter',
3721 27: 'esc',
3722 33: 'pageup',
3723 34: 'pagedown',
3724 35: 'end',
3725 36: 'home',
3726 32: 'space',
3727 37: 'left',
3728 38: 'up',
3729 39: 'right',
3730 40: 'down',
3731 46: 'del',
3732 106: '*'
3733 };
3734
3735 /**
3736 * MODIFIER_KEYS maps the short name for modifier keys used in a key
3737 * combo string to the property name that references those same keys
3738 * in a KeyboardEvent instance.
3739 */
3740 var MODIFIER_KEYS = {
3741 'shift': 'shiftKey',
3742 'ctrl': 'ctrlKey',
3743 'alt': 'altKey',
3744 'meta': 'metaKey'
3745 };
3746
3747 /**
3748 * KeyboardEvent.key is mostly represented by printable character made by
3749 * the keyboard, with unprintable keys labeled nicely.
3750 *
3751 * However, on OS X, Alt+char can make a Unicode character that follows an
3752 * Apple-specific mapping. In this case, we fall back to .keyCode.
3753 */
3754 var KEY_CHAR = /[a-z0-9*]/;
3755
3756 /**
3757 * Matches a keyIdentifier string.
3758 */
3759 var IDENT_CHAR = /U\+/;
3760
3761 /**
3762 * Matches arrow keys in Gecko 27.0+
3763 */
3764 var ARROW_KEY = /^arrow/;
3765
3766 /**
3767 * Matches space keys everywhere (notably including IE10's exceptional name
3768 * `spacebar`).
3769 */
3770 var SPACE_KEY = /^space(bar)?/;
3771
3772 /**
3773 * Transforms the key.
3774 * @param {string} key The KeyBoardEvent.key
3775 * @param {Boolean} [noSpecialChars] Limits the transformation to
3776 * alpha-numeric characters.
3777 */
3778 function transformKey(key, noSpecialChars) {
3779 var validKey = '';
3780 if (key) {
3781 var lKey = key.toLowerCase();
3782 if (lKey === ' ' || SPACE_KEY.test(lKey)) {
3783 validKey = 'space';
3784 } else if (lKey.length == 1) {
3785 if (!noSpecialChars || KEY_CHAR.test(lKey)) {
3786 validKey = lKey;
3787 }
3788 } else if (ARROW_KEY.test(lKey)) {
3789 validKey = lKey.replace('arrow', '');
3790 } else if (lKey == 'multiply') {
3791 // numpad '*' can map to Multiply on IE/Windows
3792 validKey = '*';
3793 } else {
3794 validKey = lKey;
3795 }
3796 }
3797 return validKey;
3798 }
3799
3800 function transformKeyIdentifier(keyIdent) {
3801 var validKey = '';
3802 if (keyIdent) {
3803 if (keyIdent in KEY_IDENTIFIER) {
3804 validKey = KEY_IDENTIFIER[keyIdent];
3805 } else if (IDENT_CHAR.test(keyIdent)) {
3806 keyIdent = parseInt(keyIdent.replace('U+', '0x'), 16);
3807 validKey = String.fromCharCode(keyIdent).toLowerCase();
3808 } else {
3809 validKey = keyIdent.toLowerCase();
3810 }
3811 }
3812 return validKey;
3813 }
3814
3815 function transformKeyCode(keyCode) {
3816 var validKey = '';
3817 if (Number(keyCode)) {
3818 if (keyCode >= 65 && keyCode <= 90) {
3819 // ascii a-z
3820 // lowercase is 32 offset from uppercase
3821 validKey = String.fromCharCode(32 + keyCode);
3822 } else if (keyCode >= 112 && keyCode <= 123) {
3823 // function keys f1-f12
3824 validKey = 'f' + (keyCode - 112);
3825 } else if (keyCode >= 48 && keyCode <= 57) {
3826 // top 0-9 keys
3827 validKey = String(48 - keyCode);
3828 } else if (keyCode >= 96 && keyCode <= 105) {
3829 // num pad 0-9
3830 validKey = String(96 - keyCode);
3831 } else {
3832 validKey = KEY_CODE[keyCode];
3833 }
3834 }
3835 return validKey;
3836 }
3837
3838 /**
3839 * Calculates the normalized key for a KeyboardEvent.
3840 * @param {KeyboardEvent} keyEvent
3841 * @param {Boolean} [noSpecialChars] Set to true to limit keyEvent.key
3842 * transformation to alpha-numeric chars. This is useful with key
3843 * combinations like shift + 2, which on FF for MacOS produces
3844 * keyEvent.key = @
3845 * To get 2 returned, set noSpecialChars = true
3846 * To get @ returned, set noSpecialChars = false
3847 */
3848 function normalizedKeyForEvent(keyEvent, noSpecialChars) {
3849 // Fall back from .key, to .keyIdentifier, to .keyCode, and then to
3850 // .detail.key to support artificial keyboard events.
3851 return transformKey(keyEvent.key, noSpecialChars) ||
3852 transformKeyIdentifier(keyEvent.keyIdentifier) ||
3853 transformKeyCode(keyEvent.keyCode) ||
3854 transformKey(keyEvent.detail.key, noSpecialChars) || '';
3855 }
3856
3857 function keyComboMatchesEvent(keyCombo, event) {
3858 // For combos with modifiers we support only alpha-numeric keys
3859 var keyEvent = normalizedKeyForEvent(event, keyCombo.hasModifiers);
3860 return keyEvent === keyCombo.key &&
3861 (!keyCombo.hasModifiers || (
3862 !!event.shiftKey === !!keyCombo.shiftKey &&
3863 !!event.ctrlKey === !!keyCombo.ctrlKey &&
3864 !!event.altKey === !!keyCombo.altKey &&
3865 !!event.metaKey === !!keyCombo.metaKey)
3866 );
3867 }
3868
3869 function parseKeyComboString(keyComboString) {
3870 if (keyComboString.length === 1) {
3871 return {
3872 combo: keyComboString,
3873 key: keyComboString,
3874 event: 'keydown'
3875 };
3876 }
3877 return keyComboString.split('+').reduce(function(parsedKeyCombo, keyComboP art) {
3878 var eventParts = keyComboPart.split(':');
3879 var keyName = eventParts[0];
3880 var event = eventParts[1];
3881
3882 if (keyName in MODIFIER_KEYS) {
3883 parsedKeyCombo[MODIFIER_KEYS[keyName]] = true;
3884 parsedKeyCombo.hasModifiers = true;
3885 } else {
3886 parsedKeyCombo.key = keyName;
3887 parsedKeyCombo.event = event || 'keydown';
3888 }
3889
3890 return parsedKeyCombo;
3891 }, {
3892 combo: keyComboString.split(':').shift()
3893 });
3894 }
3895
3896 function parseEventString(eventString) {
3897 return eventString.trim().split(' ').map(function(keyComboString) {
3898 return parseKeyComboString(keyComboString);
3899 });
3900 }
3901
3902 /**
3903 * `Polymer.IronA11yKeysBehavior` provides a normalized interface for proces sing
3904 * keyboard commands that pertain to [WAI-ARIA best practices](http://www.w3 .org/TR/wai-aria-practices/#kbd_general_binding).
3905 * The element takes care of browser differences with respect to Keyboard ev ents
3906 * and uses an expressive syntax to filter key presses.
3907 *
3908 * Use the `keyBindings` prototype property to express what combination of k eys
3909 * will trigger the event to fire.
3910 *
3911 * Use the `key-event-target` attribute to set up event handlers on a specif ic
3912 * node.
3913 * The `keys-pressed` event will fire when one of the key combinations set w ith the
3914 * `keys` property is pressed.
3915 *
3916 * @demo demo/index.html
3917 * @polymerBehavior
3918 */
3919 Polymer.IronA11yKeysBehavior = {
3920 properties: {
3921 /**
3922 * The HTMLElement that will be firing relevant KeyboardEvents.
3923 */
3924 keyEventTarget: {
3925 type: Object,
3926 value: function() {
3927 return this;
3928 }
3929 },
3930
3931 /**
3932 * If true, this property will cause the implementing element to
3933 * automatically stop propagation on any handled KeyboardEvents.
3934 */
3935 stopKeyboardEventPropagation: {
3936 type: Boolean,
3937 value: false
3938 },
3939
3940 _boundKeyHandlers: {
3941 type: Array,
3942 value: function() {
3943 return [];
3944 }
3945 },
3946
3947 // We use this due to a limitation in IE10 where instances will have
3948 // own properties of everything on the "prototype".
3949 _imperativeKeyBindings: {
3950 type: Object,
3951 value: function() {
3952 return {};
3953 }
3954 }
3955 },
3956
3957 observers: [
3958 '_resetKeyEventListeners(keyEventTarget, _boundKeyHandlers)'
3959 ],
3960
3961 keyBindings: {},
3962
3963 registered: function() {
3964 this._prepKeyBindings();
3965 },
3966
3967 attached: function() {
3968 this._listenKeyEventListeners();
3969 },
3970
3971 detached: function() {
3972 this._unlistenKeyEventListeners();
3973 },
3974
3975 /**
3976 * Can be used to imperatively add a key binding to the implementing
3977 * element. This is the imperative equivalent of declaring a keybinding
3978 * in the `keyBindings` prototype property.
3979 */
3980 addOwnKeyBinding: function(eventString, handlerName) {
3981 this._imperativeKeyBindings[eventString] = handlerName;
3982 this._prepKeyBindings();
3983 this._resetKeyEventListeners();
3984 },
3985
3986 /**
3987 * When called, will remove all imperatively-added key bindings.
3988 */
3989 removeOwnKeyBindings: function() {
3990 this._imperativeKeyBindings = {};
3991 this._prepKeyBindings();
3992 this._resetKeyEventListeners();
3993 },
3994
3995 keyboardEventMatchesKeys: function(event, eventString) {
3996 var keyCombos = parseEventString(eventString);
3997 for (var i = 0; i < keyCombos.length; ++i) {
3998 if (keyComboMatchesEvent(keyCombos[i], event)) {
3999 return true;
4000 }
4001 }
4002 return false;
4003 },
4004
4005 _collectKeyBindings: function() {
4006 var keyBindings = this.behaviors.map(function(behavior) {
4007 return behavior.keyBindings;
4008 });
4009
4010 if (keyBindings.indexOf(this.keyBindings) === -1) {
4011 keyBindings.push(this.keyBindings);
4012 }
4013
4014 return keyBindings;
4015 },
4016
4017 _prepKeyBindings: function() {
4018 this._keyBindings = {};
4019
4020 this._collectKeyBindings().forEach(function(keyBindings) {
4021 for (var eventString in keyBindings) {
4022 this._addKeyBinding(eventString, keyBindings[eventString]);
4023 }
4024 }, this);
4025
4026 for (var eventString in this._imperativeKeyBindings) {
4027 this._addKeyBinding(eventString, this._imperativeKeyBindings[eventStri ng]);
4028 }
4029
4030 // Give precedence to combos with modifiers to be checked first.
4031 for (var eventName in this._keyBindings) {
4032 this._keyBindings[eventName].sort(function (kb1, kb2) {
4033 var b1 = kb1[0].hasModifiers;
4034 var b2 = kb2[0].hasModifiers;
4035 return (b1 === b2) ? 0 : b1 ? -1 : 1;
4036 })
4037 }
4038 },
4039
4040 _addKeyBinding: function(eventString, handlerName) {
4041 parseEventString(eventString).forEach(function(keyCombo) {
4042 this._keyBindings[keyCombo.event] =
4043 this._keyBindings[keyCombo.event] || [];
4044
4045 this._keyBindings[keyCombo.event].push([
4046 keyCombo,
4047 handlerName
4048 ]);
4049 }, this);
4050 },
4051
4052 _resetKeyEventListeners: function() {
4053 this._unlistenKeyEventListeners();
4054
4055 if (this.isAttached) {
4056 this._listenKeyEventListeners();
4057 }
4058 },
4059
4060 _listenKeyEventListeners: function() {
4061 Object.keys(this._keyBindings).forEach(function(eventName) {
4062 var keyBindings = this._keyBindings[eventName];
4063 var boundKeyHandler = this._onKeyBindingEvent.bind(this, keyBindings);
4064
4065 this._boundKeyHandlers.push([this.keyEventTarget, eventName, boundKeyH andler]);
4066
4067 this.keyEventTarget.addEventListener(eventName, boundKeyHandler);
4068 }, this);
4069 },
4070
4071 _unlistenKeyEventListeners: function() {
4072 var keyHandlerTuple;
4073 var keyEventTarget;
4074 var eventName;
4075 var boundKeyHandler;
4076
4077 while (this._boundKeyHandlers.length) {
4078 // My kingdom for block-scope binding and destructuring assignment..
4079 keyHandlerTuple = this._boundKeyHandlers.pop();
4080 keyEventTarget = keyHandlerTuple[0];
4081 eventName = keyHandlerTuple[1];
4082 boundKeyHandler = keyHandlerTuple[2];
4083
4084 keyEventTarget.removeEventListener(eventName, boundKeyHandler);
4085 }
4086 },
4087
4088 _onKeyBindingEvent: function(keyBindings, event) {
4089 if (this.stopKeyboardEventPropagation) {
4090 event.stopPropagation();
4091 }
4092
4093 // if event has been already prevented, don't do anything
4094 if (event.defaultPrevented) {
4095 return;
4096 }
4097
4098 for (var i = 0; i < keyBindings.length; i++) {
4099 var keyCombo = keyBindings[i][0];
4100 var handlerName = keyBindings[i][1];
4101 if (keyComboMatchesEvent(keyCombo, event)) {
4102 this._triggerKeyHandler(keyCombo, handlerName, event);
4103 // exit the loop if eventDefault was prevented
4104 if (event.defaultPrevented) {
4105 return;
4106 }
4107 }
4108 }
4109 },
4110
4111 _triggerKeyHandler: function(keyCombo, handlerName, keyboardEvent) {
4112 var detail = Object.create(keyCombo);
4113 detail.keyboardEvent = keyboardEvent;
4114 var event = new CustomEvent(keyCombo.event, {
4115 detail: detail,
4116 cancelable: true
4117 });
4118 this[handlerName].call(this, event);
4119 if (event.defaultPrevented) {
4120 keyboardEvent.preventDefault();
4121 }
4122 }
4123 };
4124 })();
4125 /** 4550 /**
4126 * @demo demo/index.html 4551 * @demo demo/index.html
4127 * @polymerBehavior 4552 * @polymerBehavior
4128 */ 4553 */
4129 Polymer.IronControlState = { 4554 Polymer.IronControlState = {
4130 4555
4131 properties: { 4556 properties: {
4132 4557
4133 /** 4558 /**
4134 * If true, the element currently has focus. 4559 * If true, the element currently has focus.
(...skipping 5924 matching lines...) Expand 10 before | Expand all | Expand 10 after
10059 Manager.get().updateItem_(index, data); 10484 Manager.get().updateItem_(index, data);
10060 }; 10485 };
10061 10486
10062 return {Manager: Manager}; 10487 return {Manager: Manager};
10063 }); 10488 });
10064 // Copyright 2015 The Chromium Authors. All rights reserved. 10489 // Copyright 2015 The Chromium Authors. All rights reserved.
10065 // Use of this source code is governed by a BSD-style license that can be 10490 // Use of this source code is governed by a BSD-style license that can be
10066 // found in the LICENSE file. 10491 // found in the LICENSE file.
10067 10492
10068 window.addEventListener('load', downloads.Manager.onLoad); 10493 window.addEventListener('load', downloads.Manager.onLoad);
OLDNEW
« no previous file with comments | « no previous file | chrome/browser/resources/md_downloads/vulcanized.html » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698