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

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

Issue 1824963002: MD Downloads: re-vulcanize to pick up new changes (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@downloads-double
Patch Set: Created 4 years, 9 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 2016 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 /**
6 * @fileoverview PromiseResolver is a helper class that allows creating a
7 * Promise that will be fulfilled (resolved or rejected) some time later.
8 *
9 * Example:
10 * var resolver = new PromiseResolver();
11 * resolver.promise.then(function(result) {
12 * console.log('resolved with', result);
13 * });
14 * ...
15 * ...
16 * resolver.resolve({hello: 'world'});
17 */
18
19 /**
20 * @constructor @struct
21 * @template T
22 */
23 function PromiseResolver() {
24 /** @type {function(T): void} */
25 this.resolve;
26
27 /** @type {function(*=): void} */
28 this.reject;
29
30 /** @type {!Promise<T>} */
31 this.promise = new Promise(function(resolve, reject) {
32 this.resolve = resolve;
33 this.reject = reject;
34 }.bind(this));
35 };
1 // Copyright (c) 2012 The Chromium Authors. All rights reserved. 36 // 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 37 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file. 38 // found in the LICENSE file.
4 39
5 /** 40 /**
6 * The global object. 41 * The global object.
7 * @type {!Object} 42 * @type {!Object}
8 * @const 43 * @const
9 */ 44 */
10 var global = this; 45 var global = this;
11 46
12 /** @typedef {{eventName: string, uid: number}} */ 47 /** @typedef {{eventName: string, uid: number}} */
13 var WebUIListener; 48 var WebUIListener;
14 49
15 /** Platform, package, object property, and Event support. **/ 50 /** Platform, package, object property, and Event support. **/
16 var cr = function() { 51 var cr = cr || function() {
17 'use strict'; 52 'use strict';
18 53
19 /** 54 /**
20 * Builds an object structure for the provided namespace path, 55 * Builds an object structure for the provided namespace path,
21 * ensuring that names that already exist are not overwritten. For 56 * ensuring that names that already exist are not overwritten. For
22 * example: 57 * example:
23 * "a.b.c" -> a = {};a.b={};a.b.c={}; 58 * "a.b.c" -> a = {};a.b={};a.b.c={};
24 * @param {string} name Name of the object that this file defines. 59 * @param {string} name Name of the object that this file defines.
25 * @param {*=} opt_object The object to expose at the end of the path. 60 * @param {*=} opt_object The object to expose at the end of the path.
26 * @param {Object=} opt_objectToExportTo The object to add the path to; 61 * @param {Object=} opt_objectToExportTo The object to add the path to;
27 * default is {@code global}. 62 * default is {@code global}.
63 * @return {!Object} The last object exported (i.e. exportPath('cr.ui')
64 * returns a reference to the ui property of window.cr).
28 * @private 65 * @private
29 */ 66 */
30 function exportPath(name, opt_object, opt_objectToExportTo) { 67 function exportPath(name, opt_object, opt_objectToExportTo) {
31 var parts = name.split('.'); 68 var parts = name.split('.');
32 var cur = opt_objectToExportTo || global; 69 var cur = opt_objectToExportTo || global;
33 70
34 for (var part; parts.length && (part = parts.shift());) { 71 for (var part; parts.length && (part = parts.shift());) {
35 if (!parts.length && opt_object !== undefined) { 72 if (!parts.length && opt_object !== undefined) {
36 // last part and we have an object; use it 73 // last part and we have an object; use it
37 cur[part] = opt_object; 74 cur[part] = opt_object;
38 } else if (part in cur) { 75 } else if (part in cur) {
39 cur = cur[part]; 76 cur = cur[part];
40 } else { 77 } else {
41 cur = cur[part] = {}; 78 cur = cur[part] = {};
42 } 79 }
43 } 80 }
44 return cur; 81 return cur;
45 }; 82 }
46 83
47 /** 84 /**
48 * Fires a property change event on the target. 85 * Fires a property change event on the target.
49 * @param {EventTarget} target The target to dispatch the event on. 86 * @param {EventTarget} target The target to dispatch the event on.
50 * @param {string} propertyName The name of the property that changed. 87 * @param {string} propertyName The name of the property that changed.
51 * @param {*} newValue The new value for the property. 88 * @param {*} newValue The new value for the property.
52 * @param {*} oldValue The old value for the property. 89 * @param {*} oldValue The old value for the property.
53 */ 90 */
54 function dispatchPropertyChange(target, propertyName, newValue, oldValue) { 91 function dispatchPropertyChange(target, propertyName, newValue, oldValue) {
55 var e = new Event(propertyName + 'Change'); 92 var e = new Event(propertyName + 'Change');
(...skipping 255 matching lines...) Expand 10 before | Expand all | Expand 10 after
311 var target = opt_target ? document.getElementById(opt_target) : 348 var target = opt_target ? document.getElementById(opt_target) :
312 ctor.getInstance(); 349 ctor.getInstance();
313 return target[method + '_'].apply(target, arguments); 350 return target[method + '_'].apply(target, arguments);
314 }; 351 };
315 }); 352 });
316 } 353 }
317 354
318 /** 355 /**
319 * The mapping used by the sendWithPromise mechanism to tie the Promise 356 * The mapping used by the sendWithPromise mechanism to tie the Promise
320 * returned to callers with the corresponding WebUI response. The mapping is 357 * returned to callers with the corresponding WebUI response. The mapping is
321 * from ID to the Promise resolver function; the ID is generated by 358 * from ID to the PromiseResolver helper; the ID is generated by
322 * sendWithPromise and is unique across all invocations of said method. 359 * sendWithPromise and is unique across all invocations of said method.
323 * @type {!Object<!Function>} 360 * @type {!Object<!PromiseResolver>}
324 */ 361 */
325 var chromeSendResolverMap = {}; 362 var chromeSendResolverMap = {};
326 363
327 /** 364 /**
328 * The named method the WebUI handler calls directly in response to a 365 * The named method the WebUI handler calls directly in response to a
329 * chrome.send call that expects a response. The handler requires no knowledge 366 * chrome.send call that expects a response. The handler requires no knowledge
330 * of the specific name of this method, as the name is passed to the handler 367 * of the specific name of this method, as the name is passed to the handler
331 * as the first argument in the arguments list of chrome.send. The handler 368 * as the first argument in the arguments list of chrome.send. The handler
332 * must pass the ID, also sent via the chrome.send arguments list, as the 369 * must pass the ID, also sent via the chrome.send arguments list, as the
333 * first argument of the JS invocation; additionally, the handler may 370 * first argument of the JS invocation; additionally, the handler may
334 * supply any number of other arguments that will be included in the response. 371 * supply any number of other arguments that will be included in the response.
335 * @param {string} id The unique ID identifying the Promise this response is 372 * @param {string} id The unique ID identifying the Promise this response is
336 * tied to. 373 * tied to.
374 * @param {boolean} isSuccess Whether the request was successful.
337 * @param {*} response The response as sent from C++. 375 * @param {*} response The response as sent from C++.
338 */ 376 */
339 function webUIResponse(id, response) { 377 function webUIResponse(id, isSuccess, response) {
340 var resolverFn = chromeSendResolverMap[id]; 378 var resolver = chromeSendResolverMap[id];
341 delete chromeSendResolverMap[id]; 379 delete chromeSendResolverMap[id];
342 resolverFn(response); 380
381 if (isSuccess)
382 resolver.resolve(response);
383 else
384 resolver.reject(response);
343 } 385 }
344 386
345 /** 387 /**
346 * A variation of chrome.send, suitable for messages that expect a single 388 * A variation of chrome.send, suitable for messages that expect a single
347 * response from C++. 389 * response from C++.
348 * @param {string} methodName The name of the WebUI handler API. 390 * @param {string} methodName The name of the WebUI handler API.
349 * @param {...*} var_args Varibale number of arguments to be forwarded to the 391 * @param {...*} var_args Varibale number of arguments to be forwarded to the
350 * C++ call. 392 * C++ call.
351 * @return {!Promise} 393 * @return {!Promise}
352 */ 394 */
353 function sendWithPromise(methodName, var_args) { 395 function sendWithPromise(methodName, var_args) {
354 var args = Array.prototype.slice.call(arguments, 1); 396 var args = Array.prototype.slice.call(arguments, 1);
355 return new Promise(function(resolve, reject) { 397 var promiseResolver = new PromiseResolver();
356 var id = methodName + '_' + createUid(); 398 var id = methodName + '_' + createUid();
357 chromeSendResolverMap[id] = resolve; 399 chromeSendResolverMap[id] = promiseResolver;
358 chrome.send(methodName, [id].concat(args)); 400 chrome.send(methodName, [id].concat(args));
359 }); 401 return promiseResolver.promise;
360 } 402 }
361 403
362 /** 404 /**
363 * A map of maps associating event names with listeners. The 2nd level map 405 * A map of maps associating event names with listeners. The 2nd level map
364 * associates a listener ID with the callback function, such that individual 406 * associates a listener ID with the callback function, such that individual
365 * listeners can be removed from an event without affecting other listeners of 407 * listeners can be removed from an event without affecting other listeners of
366 * the same event. 408 * the same event.
367 * @type {!Object<!Object<!Function>>} 409 * @type {!Object<!Object<!Function>>}
368 */ 410 */
369 var webUIListenerMap = {}; 411 var webUIListenerMap = {};
(...skipping 1093 matching lines...) Expand 10 before | Expand all | Expand 10 after
1463 * @param {string} original The original string. 1505 * @param {string} original The original string.
1464 * @param {number} maxLength The maximum length allowed for the string. 1506 * @param {number} maxLength The maximum length allowed for the string.
1465 * @return {string} The original string if its length does not exceed 1507 * @return {string} The original string if its length does not exceed
1466 * |maxLength|. Otherwise the first |maxLength| - 1 characters with '...' 1508 * |maxLength|. Otherwise the first |maxLength| - 1 characters with '...'
1467 * appended. 1509 * appended.
1468 */ 1510 */
1469 function elide(original, maxLength) { 1511 function elide(original, maxLength) {
1470 if (original.length <= maxLength) 1512 if (original.length <= maxLength)
1471 return original; 1513 return original;
1472 return original.substring(0, maxLength - 1) + '\u2026'; 1514 return original.substring(0, maxLength - 1) + '\u2026';
1515 }
1516
1517 /**
1518 * Quote a string so it can be used in a regular expression.
1519 * @param {string} str The source string.
1520 * @return {string} The escaped string.
1521 */
1522 function quoteString(str) {
1523 return str.replace(/([\\\.\+\*\?\[\^\]\$\(\)\{\}\=\!\<\>\|\:])/g, '\\$1');
1473 }; 1524 };
1474 // Copyright (c) 2013 The Chromium Authors. All rights reserved. 1525 // Copyright (c) 2013 The Chromium Authors. All rights reserved.
1475 // Use of this source code is governed by a BSD-style license that can be 1526 // Use of this source code is governed by a BSD-style license that can be
1476 // found in the LICENSE file. 1527 // found in the LICENSE file.
1477 1528
1478 /** 1529 /**
1479 * @fileoverview Assertion support. 1530 * @fileoverview Assertion support.
1480 */ 1531 */
1481 1532
1482 /** 1533 /**
(...skipping 350 matching lines...) Expand 10 before | Expand all | Expand 10 after
1833 1884
1834 // <include src="../../../../ui/webui/resources/js/i18n_template_no_process.js"> 1885 // <include src="../../../../ui/webui/resources/js/i18n_template_no_process.js">
1835 1886
1836 i18nTemplate.process(document, loadTimeData); 1887 i18nTemplate.process(document, loadTimeData);
1837 /** 1888 /**
1838 * `IronResizableBehavior` is a behavior that can be used in Polymer elements to 1889 * `IronResizableBehavior` is a behavior that can be used in Polymer elements to
1839 * coordinate the flow of resize events between "resizers" (elements that cont rol the 1890 * coordinate the flow of resize events between "resizers" (elements that cont rol the
1840 * size or hidden state of their children) and "resizables" (elements that nee d to be 1891 * size or hidden state of their children) and "resizables" (elements that nee d to be
1841 * notified when they are resized or un-hidden by their parents in order to ta ke 1892 * notified when they are resized or un-hidden by their parents in order to ta ke
1842 * action on their new measurements). 1893 * action on their new measurements).
1894 *
1843 * Elements that perform measurement should add the `IronResizableBehavior` be havior to 1895 * Elements that perform measurement should add the `IronResizableBehavior` be havior to
1844 * their element definition and listen for the `iron-resize` event on themselv es. 1896 * their element definition and listen for the `iron-resize` event on themselv es.
1845 * This event will be fired when they become showing after having been hidden, 1897 * This event will be fired when they become showing after having been hidden,
1846 * when they are resized explicitly by another resizable, or when the window h as been 1898 * when they are resized explicitly by another resizable, or when the window h as been
1847 * resized. 1899 * resized.
1900 *
1848 * Note, the `iron-resize` event is non-bubbling. 1901 * Note, the `iron-resize` event is non-bubbling.
1849 * 1902 *
1850 * @polymerBehavior Polymer.IronResizableBehavior 1903 * @polymerBehavior Polymer.IronResizableBehavior
1851 * @demo demo/index.html 1904 * @demo demo/index.html
1852 **/ 1905 **/
1853 Polymer.IronResizableBehavior = { 1906 Polymer.IronResizableBehavior = {
1854 properties: { 1907 properties: {
1855 /** 1908 /**
1856 * The closest ancestor element that implements `IronResizableBehavior`. 1909 * The closest ancestor element that implements `IronResizableBehavior`.
1857 */ 1910 */
(...skipping 643 matching lines...) Expand 10 before | Expand all | Expand 10 after
2501 window.removeEventListener('scroll', this._boundScrollHandler); 2554 window.removeEventListener('scroll', this._boundScrollHandler);
2502 } else if (this._oldScrollTarget.removeEventListener) { 2555 } else if (this._oldScrollTarget.removeEventListener) {
2503 this._oldScrollTarget.removeEventListener('scroll', this._boundScrollH andler); 2556 this._oldScrollTarget.removeEventListener('scroll', this._boundScrollH andler);
2504 } 2557 }
2505 this._oldScrollTarget = null; 2558 this._oldScrollTarget = null;
2506 } 2559 }
2507 if (isAttached) { 2560 if (isAttached) {
2508 // Support element id references 2561 // Support element id references
2509 if (typeof scrollTarget === 'string') { 2562 if (typeof scrollTarget === 'string') {
2510 2563
2511 var ownerRoot = Polymer.dom(this).getOwnerRoot(); 2564 var host = this.domHost;
2512 this.scrollTarget = (ownerRoot && ownerRoot.$) ? 2565 this.scrollTarget = host && host.$ ? host.$[scrollTarget] :
2513 ownerRoot.$[scrollTarget] : Polymer.dom(this.ownerDocument).queryS elector('#' + scrollTarget); 2566 Polymer.dom(this.ownerDocument).querySelector('#' + scrollTarget);
2514 2567
2515 } else if (this._scrollHandler) { 2568 } else if (this._scrollHandler) {
2516 2569
2517 this._boundScrollHandler = this._boundScrollHandler || this._scrollHan dler.bind(this); 2570 this._boundScrollHandler = this._boundScrollHandler || this._scrollHan dler.bind(this);
2518 // Add a new listener 2571 // Add a new listener
2519 if (scrollTarget === this._doc) { 2572 if (scrollTarget === this._doc) {
2520 window.addEventListener('scroll', this._boundScrollHandler); 2573 window.addEventListener('scroll', this._boundScrollHandler);
2521 if (this._scrollTop !== 0 || this._scrollLeft !== 0) { 2574 if (this._scrollTop !== 0 || this._scrollLeft !== 0) {
2522 this._scrollHandler(); 2575 this._scrollHandler();
2523 } 2576 }
(...skipping 78 matching lines...) Expand 10 before | Expand all | Expand 10 after
2602 window.scrollTo(left, window.pageYOffset); 2655 window.scrollTo(left, window.pageYOffset);
2603 } else if (this._isValidScrollTarget()) { 2656 } else if (this._isValidScrollTarget()) {
2604 this.scrollTarget.scrollLeft = left; 2657 this.scrollTarget.scrollLeft = left;
2605 } 2658 }
2606 }, 2659 },
2607 2660
2608 /** 2661 /**
2609 * Scrolls the content to a particular place. 2662 * Scrolls the content to a particular place.
2610 * 2663 *
2611 * @method scroll 2664 * @method scroll
2665 * @param {number} left The left position
2612 * @param {number} top The top position 2666 * @param {number} top The top position
2613 * @param {number} left The left position
2614 */ 2667 */
2615 scroll: function(top, left) { 2668 scroll: function(left, top) {
2616 if (this.scrollTarget === this._doc) { 2669 if (this.scrollTarget === this._doc) {
2617 window.scrollTo(top, left); 2670 window.scrollTo(left, top);
2618 } else if (this._isValidScrollTarget()) { 2671 } else if (this._isValidScrollTarget()) {
2672 this.scrollTarget.scrollLeft = left;
2619 this.scrollTarget.scrollTop = top; 2673 this.scrollTarget.scrollTop = top;
2620 this.scrollTarget.scrollLeft = left;
2621 } 2674 }
2622 }, 2675 },
2623 2676
2624 /** 2677 /**
2625 * Gets the width of the scroll target. 2678 * Gets the width of the scroll target.
2626 * 2679 *
2627 * @type {number} 2680 * @type {number}
2628 */ 2681 */
2629 get _scrollTargetWidth() { 2682 get _scrollTargetWidth() {
2630 if (this._isValidScrollTarget()) { 2683 if (this._isValidScrollTarget()) {
(...skipping 136 matching lines...) Expand 10 before | Expand all | Expand 10 after
2767 'enter': '_didEnter' 2820 'enter': '_didEnter'
2768 }, 2821 },
2769 2822
2770 /** 2823 /**
2771 * The ratio of hidden tiles that should remain in the scroll direction. 2824 * The ratio of hidden tiles that should remain in the scroll direction.
2772 * Recommended value ~0.5, so it will distribute tiles evely in both directi ons. 2825 * Recommended value ~0.5, so it will distribute tiles evely in both directi ons.
2773 */ 2826 */
2774 _ratio: 0.5, 2827 _ratio: 0.5,
2775 2828
2776 /** 2829 /**
2777 * The padding-top value of the `scroller` element 2830 * The padding-top value for the list.
2778 */ 2831 */
2779 _scrollerPaddingTop: 0, 2832 _scrollerPaddingTop: 0,
2780 2833
2781 /** 2834 /**
2782 * This value is the same as `scrollTop`. 2835 * This value is the same as `scrollTop`.
2783 */ 2836 */
2784 _scrollPosition: 0, 2837 _scrollPosition: 0,
2785 2838
2786 /** 2839 /**
2787 * The number of tiles in the DOM.
2788 */
2789 _physicalCount: 0,
2790
2791 /**
2792 * The k-th tile that is at the top of the scrolling list.
2793 */
2794 _physicalStart: 0,
2795
2796 /**
2797 * The k-th tile that is at the bottom of the scrolling list.
2798 */
2799 _physicalEnd: 0,
2800
2801 /**
2802 * The sum of the heights of all the tiles in the DOM. 2840 * The sum of the heights of all the tiles in the DOM.
2803 */ 2841 */
2804 _physicalSize: 0, 2842 _physicalSize: 0,
2805 2843
2806 /** 2844 /**
2807 * The average `F` of the tiles observed till now. 2845 * The average `F` of the tiles observed till now.
2808 */ 2846 */
2809 _physicalAverage: 0, 2847 _physicalAverage: 0,
2810 2848
2811 /** 2849 /**
2812 * The number of tiles which `offsetHeight` > 0 observed until now. 2850 * The number of tiles which `offsetHeight` > 0 observed until now.
2813 */ 2851 */
2814 _physicalAverageCount: 0, 2852 _physicalAverageCount: 0,
2815 2853
2816 /** 2854 /**
2817 * The Y position of the item rendered in the `_physicalStart` 2855 * The Y position of the item rendered in the `_physicalStart`
2818 * tile relative to the scrolling list. 2856 * tile relative to the scrolling list.
2819 */ 2857 */
2820 _physicalTop: 0, 2858 _physicalTop: 0,
2821 2859
2822 /** 2860 /**
2823 * The number of items in the list. 2861 * The number of items in the list.
2824 */ 2862 */
2825 _virtualCount: 0, 2863 _virtualCount: 0,
2826 2864
2827 /** 2865 /**
2828 * The n-th item rendered in the `_physicalStart` tile.
2829 */
2830 _virtualStartVal: 0,
2831
2832 /**
2833 * A map between an item key and its physical item index 2866 * A map between an item key and its physical item index
2834 */ 2867 */
2835 _physicalIndexForKey: null, 2868 _physicalIndexForKey: null,
2836 2869
2837 /** 2870 /**
2838 * The estimated scroll height based on `_physicalAverage` 2871 * The estimated scroll height based on `_physicalAverage`
2839 */ 2872 */
2840 _estScrollHeight: 0, 2873 _estScrollHeight: 0,
2841 2874
2842 /** 2875 /**
(...skipping 25 matching lines...) Expand all
2868 */ 2901 */
2869 _firstVisibleIndexVal: null, 2902 _firstVisibleIndexVal: null,
2870 2903
2871 /** 2904 /**
2872 * A cached value for the last visible index. 2905 * A cached value for the last visible index.
2873 * See `lastVisibleIndex` 2906 * See `lastVisibleIndex`
2874 * @type {?number} 2907 * @type {?number}
2875 */ 2908 */
2876 _lastVisibleIndexVal: null, 2909 _lastVisibleIndexVal: null,
2877 2910
2878
2879 /** 2911 /**
2880 * A Polymer collection for the items. 2912 * A Polymer collection for the items.
2881 * @type {?Polymer.Collection} 2913 * @type {?Polymer.Collection}
2882 */ 2914 */
2883 _collection: null, 2915 _collection: null,
2884 2916
2885 /** 2917 /**
2886 * True if the current item list was rendered for the first time 2918 * True if the current item list was rendered for the first time
2887 * after attached. 2919 * after attached.
2888 */ 2920 */
2889 _itemsRendered: false, 2921 _itemsRendered: false,
2890 2922
2891 /** 2923 /**
2892 * The page that is currently rendered. 2924 * The page that is currently rendered.
2893 */ 2925 */
2894 _lastPage: null, 2926 _lastPage: null,
2895 2927
2896 /** 2928 /**
2897 * The max number of pages to render. One page is equivalent to the height o f the list. 2929 * The max number of pages to render. One page is equivalent to the height o f the list.
2898 */ 2930 */
2899 _maxPages: 3, 2931 _maxPages: 3,
2900 2932
2901 /** 2933 /**
2902 * The currently focused item index. 2934 * The currently focused physical item.
2903 */ 2935 */
2904 _focusedIndex: 0, 2936 _focusedItem: null,
2937
2938 /**
2939 * The index of the `_focusedItem`.
2940 */
2941 _focusedIndex: -1,
2905 2942
2906 /** 2943 /**
2907 * The the item that is focused if it is moved offscreen. 2944 * The the item that is focused if it is moved offscreen.
2908 * @private {?TemplatizerNode} 2945 * @private {?TemplatizerNode}
2909 */ 2946 */
2910 _offscreenFocusedItem: null, 2947 _offscreenFocusedItem: null,
2911 2948
2912 /** 2949 /**
2913 * The item that backfills the `_offscreenFocusedItem` in the physical items 2950 * The item that backfills the `_offscreenFocusedItem` in the physical items
2914 * list when that item is moved offscreen. 2951 * list when that item is moved offscreen.
(...skipping 15 matching lines...) Expand all
2930 }, 2967 },
2931 2968
2932 /** 2969 /**
2933 * The n-th item rendered in the last physical item. 2970 * The n-th item rendered in the last physical item.
2934 */ 2971 */
2935 get _virtualEnd() { 2972 get _virtualEnd() {
2936 return this._virtualStart + this._physicalCount - 1; 2973 return this._virtualStart + this._physicalCount - 1;
2937 }, 2974 },
2938 2975
2939 /** 2976 /**
2977 * The height of the physical content that isn't on the screen.
2978 */
2979 get _hiddenContentSize() {
2980 return this._physicalSize - this._viewportSize;
2981 },
2982
2983 /**
2984 * The maximum scroll top value.
2985 */
2986 get _maxScrollTop() {
2987 return this._estScrollHeight - this._viewportSize + this._scrollerPaddingT op;
2988 },
2989
2990 /**
2940 * The lowest n-th value for an item such that it can be rendered in `_physi calStart`. 2991 * The lowest n-th value for an item such that it can be rendered in `_physi calStart`.
2941 */ 2992 */
2942 _minVirtualStart: 0, 2993 _minVirtualStart: 0,
2943 2994
2944 /** 2995 /**
2945 * The largest n-th value for an item such that it can be rendered in `_phys icalStart`. 2996 * The largest n-th value for an item such that it can be rendered in `_phys icalStart`.
2946 */ 2997 */
2947 get _maxVirtualStart() { 2998 get _maxVirtualStart() {
2948 return Math.max(0, this._virtualCount - this._physicalCount); 2999 return Math.max(0, this._virtualCount - this._physicalCount);
2949 }, 3000 },
2950 3001
2951 /** 3002 /**
2952 * The height of the physical content that isn't on the screen. 3003 * The n-th item rendered in the `_physicalStart` tile.
2953 */ 3004 */
2954 get _hiddenContentSize() { 3005 _virtualStartVal: 0,
2955 return this._physicalSize - this._viewportSize; 3006
3007 set _virtualStart(val) {
3008 this._virtualStartVal = Math.min(this._maxVirtualStart, Math.max(this._min VirtualStart, val));
3009 },
3010
3011 get _virtualStart() {
3012 return this._virtualStartVal || 0;
2956 }, 3013 },
2957 3014
2958 /** 3015 /**
2959 * The maximum scroll top value. 3016 * The k-th tile that is at the top of the scrolling list.
2960 */ 3017 */
2961 get _maxScrollTop() { 3018 _physicalStartVal: 0,
2962 return this._estScrollHeight - this._viewportSize; 3019
3020 set _physicalStart(val) {
3021 this._physicalStartVal = val % this._physicalCount;
3022 if (this._physicalStartVal < 0) {
3023 this._physicalStartVal = this._physicalCount + this._physicalStartVal;
3024 }
3025 this._physicalEnd = (this._physicalStart + this._physicalCount - 1) % this ._physicalCount;
3026 },
3027
3028 get _physicalStart() {
3029 return this._physicalStartVal || 0;
2963 }, 3030 },
2964 3031
2965 /** 3032 /**
2966 * Sets the n-th item rendered in `_physicalStart` 3033 * The number of tiles in the DOM.
2967 */ 3034 */
2968 set _virtualStart(val) { 3035 _physicalCountVal: 0,
2969 // clamp the value so that _minVirtualStart <= val <= _maxVirtualStart 3036
2970 this._virtualStartVal = Math.min(this._maxVirtualStart, Math.max(this._min VirtualStart, val)); 3037 set _physicalCount(val) {
2971 if (this._physicalCount === 0) { 3038 this._physicalCountVal = val;
2972 this._physicalStart = 0; 3039 this._physicalEnd = (this._physicalStart + this._physicalCount - 1) % this ._physicalCount;
2973 this._physicalEnd = 0; 3040 },
2974 } else { 3041
2975 this._physicalStart = this._virtualStartVal % this._physicalCount; 3042 get _physicalCount() {
2976 this._physicalEnd = (this._physicalStart + this._physicalCount - 1) % th is._physicalCount; 3043 return this._physicalCountVal;
2977 }
2978 }, 3044 },
2979 3045
2980 /** 3046 /**
2981 * Gets the n-th item rendered in `_physicalStart` 3047 * The k-th tile that is at the bottom of the scrolling list.
2982 */ 3048 */
2983 get _virtualStart() { 3049 _physicalEnd: 0,
2984 return this._virtualStartVal;
2985 },
2986 3050
2987 /** 3051 /**
2988 * An optimal physical size such that we will have enough physical items 3052 * An optimal physical size such that we will have enough physical items
2989 * to fill up the viewport and recycle when the user scrolls. 3053 * to fill up the viewport and recycle when the user scrolls.
2990 * 3054 *
2991 * This default value assumes that we will at least have the equivalent 3055 * This default value assumes that we will at least have the equivalent
2992 * to a viewport of physical items above and below the user's viewport. 3056 * to a viewport of physical items above and below the user's viewport.
2993 */ 3057 */
2994 get _optPhysicalSize() { 3058 get _optPhysicalSize() {
2995 return this._viewportSize * this._maxPages; 3059 return this._viewportSize * this._maxPages;
2996 }, 3060 },
2997 3061
2998 /** 3062 /**
2999 * True if the current list is visible. 3063 * True if the current list is visible.
3000 */ 3064 */
3001 get _isVisible() { 3065 get _isVisible() {
3002 return this.scrollTarget && Boolean(this.scrollTarget.offsetWidth || this. scrollTarget.offsetHeight); 3066 return this.scrollTarget && Boolean(this.scrollTarget.offsetWidth || this. scrollTarget.offsetHeight);
3003 }, 3067 },
3004 3068
3005 /** 3069 /**
3006 * Gets the index of the first visible item in the viewport. 3070 * Gets the index of the first visible item in the viewport.
3007 * 3071 *
3008 * @type {number} 3072 * @type {number}
3009 */ 3073 */
3010 get firstVisibleIndex() { 3074 get firstVisibleIndex() {
3011 if (this._firstVisibleIndexVal === null) { 3075 if (this._firstVisibleIndexVal === null) {
3012 var physicalOffset = this._physicalTop; 3076 var physicalOffset = this._physicalTop + this._scrollerPaddingTop;
3013 3077
3014 this._firstVisibleIndexVal = this._iterateItems( 3078 this._firstVisibleIndexVal = this._iterateItems(
3015 function(pidx, vidx) { 3079 function(pidx, vidx) {
3016 physicalOffset += this._physicalSizes[pidx]; 3080 physicalOffset += this._physicalSizes[pidx];
3017
3018 if (physicalOffset > this._scrollPosition) { 3081 if (physicalOffset > this._scrollPosition) {
3019 return vidx; 3082 return vidx;
3020 } 3083 }
3021 }) || 0; 3084 }) || 0;
3022 } 3085 }
3023 return this._firstVisibleIndexVal; 3086 return this._firstVisibleIndexVal;
3024 }, 3087 },
3025 3088
3026 /** 3089 /**
3027 * Gets the index of the last visible item in the viewport. 3090 * Gets the index of the last visible item in the viewport.
3028 * 3091 *
3029 * @type {number} 3092 * @type {number}
3030 */ 3093 */
3031 get lastVisibleIndex() { 3094 get lastVisibleIndex() {
3032 if (this._lastVisibleIndexVal === null) { 3095 if (this._lastVisibleIndexVal === null) {
3033 var physicalOffset = this._physicalTop; 3096 var physicalOffset = this._physicalTop;
3034 3097
3035 this._iterateItems(function(pidx, vidx) { 3098 this._iterateItems(function(pidx, vidx) {
3036 physicalOffset += this._physicalSizes[pidx]; 3099 physicalOffset += this._physicalSizes[pidx];
3037 3100
3038 if(physicalOffset <= this._scrollBottom) { 3101 if (physicalOffset <= this._scrollBottom) {
3039 this._lastVisibleIndexVal = vidx; 3102 this._lastVisibleIndexVal = vidx;
3040 } 3103 }
3041 }); 3104 });
3042 } 3105 }
3043 return this._lastVisibleIndexVal; 3106 return this._lastVisibleIndexVal;
3044 }, 3107 },
3045 3108
3109 get _defaultScrollTarget() {
3110 return this;
3111 },
3112
3046 ready: function() { 3113 ready: function() {
3047 this.addEventListener('focus', this._didFocus.bind(this), true); 3114 this.addEventListener('focus', this._didFocus.bind(this), true);
3048 }, 3115 },
3049 3116
3050 attached: function() { 3117 attached: function() {
3051 this.updateViewportBoundaries(); 3118 this.updateViewportBoundaries();
3052 this._render(); 3119 this._render();
3053 }, 3120 },
3054 3121
3055 detached: function() { 3122 detached: function() {
3056 this._itemsRendered = false; 3123 this._itemsRendered = false;
3057 }, 3124 },
3058 3125
3059 get _defaultScrollTarget() {
3060 return this;
3061 },
3062
3063 /** 3126 /**
3064 * Set the overflow property if this element has its own scrolling region 3127 * Set the overflow property if this element has its own scrolling region
3065 */ 3128 */
3066 _setOverflow: function(scrollTarget) { 3129 _setOverflow: function(scrollTarget) {
3067 this.style.webkitOverflowScrolling = scrollTarget === this ? 'touch' : ''; 3130 this.style.webkitOverflowScrolling = scrollTarget === this ? 'touch' : '';
3068 this.style.overflow = scrollTarget === this ? 'auto' : ''; 3131 this.style.overflow = scrollTarget === this ? 'auto' : '';
3069 }, 3132 },
3070 3133
3071 /** 3134 /**
3072 * Invoke this method if you dynamically update the viewport's 3135 * Invoke this method if you dynamically update the viewport's
3073 * size or CSS padding. 3136 * size or CSS padding.
3074 * 3137 *
3075 * @method updateViewportBoundaries 3138 * @method updateViewportBoundaries
3076 */ 3139 */
3077 updateViewportBoundaries: function() { 3140 updateViewportBoundaries: function() {
3078 var scrollerStyle = window.getComputedStyle(this.scrollTarget); 3141 this._scrollerPaddingTop = this.scrollTarget === this ? 0 :
3079 this._scrollerPaddingTop = parseInt(scrollerStyle['padding-top'], 10); 3142 parseInt(window.getComputedStyle(this)['padding-top'], 10);
3143
3080 this._viewportSize = this._scrollTargetHeight; 3144 this._viewportSize = this._scrollTargetHeight;
3081 }, 3145 },
3082 3146
3083 /** 3147 /**
3084 * Update the models, the position of the 3148 * Update the models, the position of the
3085 * items in the viewport and recycle tiles as needed. 3149 * items in the viewport and recycle tiles as needed.
3086 */ 3150 */
3087 _scrollHandler: function() { 3151 _scrollHandler: function() {
3088 // clamp the `scrollTop` value 3152 // clamp the `scrollTop` value
3089 // IE 10|11 scrollTop may go above `_maxScrollTop`
3090 // iOS `scrollTop` may go below 0 and above `_maxScrollTop`
3091 var scrollTop = Math.max(0, Math.min(this._maxScrollTop, this._scrollTop)) ; 3153 var scrollTop = Math.max(0, Math.min(this._maxScrollTop, this._scrollTop)) ;
3154 var delta = scrollTop - this._scrollPosition;
3092 var tileHeight, tileTop, kth, recycledTileSet, scrollBottom, physicalBotto m; 3155 var tileHeight, tileTop, kth, recycledTileSet, scrollBottom, physicalBotto m;
3093 var ratio = this._ratio; 3156 var ratio = this._ratio;
3094 var delta = scrollTop - this._scrollPosition;
3095 var recycledTiles = 0; 3157 var recycledTiles = 0;
3096 var hiddenContentSize = this._hiddenContentSize; 3158 var hiddenContentSize = this._hiddenContentSize;
3097 var currentRatio = ratio; 3159 var currentRatio = ratio;
3098 var movingUp = []; 3160 var movingUp = [];
3099 3161
3100 // track the last `scrollTop` 3162 // track the last `scrollTop`
3101 this._scrollPosition = scrollTop; 3163 this._scrollPosition = scrollTop;
3102 3164
3103 // clear cached visible index 3165 // clear cached visible indexes
3104 this._firstVisibleIndexVal = null; 3166 this._firstVisibleIndexVal = null;
3105 this._lastVisibleIndexVal = null; 3167 this._lastVisibleIndexVal = null;
3106 3168
3107 scrollBottom = this._scrollBottom; 3169 scrollBottom = this._scrollBottom;
3108 physicalBottom = this._physicalBottom; 3170 physicalBottom = this._physicalBottom;
3109 3171
3110 // random access 3172 // random access
3111 if (Math.abs(delta) > this._physicalSize) { 3173 if (Math.abs(delta) > this._physicalSize) {
3112 this._physicalTop += delta; 3174 this._physicalTop += delta;
3113 recycledTiles = Math.round(delta / this._physicalAverage); 3175 recycledTiles = Math.round(delta / this._physicalAverage);
(...skipping 66 matching lines...) Expand 10 before | Expand all | Expand 10 after
3180 3242
3181 if (recycledTiles === 0) { 3243 if (recycledTiles === 0) {
3182 // If the list ever reach this case, the physical average is not signifi cant enough 3244 // If the list ever reach this case, the physical average is not signifi cant enough
3183 // to create all the items needed to cover the entire viewport. 3245 // to create all the items needed to cover the entire viewport.
3184 // e.g. A few items have a height that differs from the average by serve ral order of magnitude. 3246 // e.g. A few items have a height that differs from the average by serve ral order of magnitude.
3185 if (physicalBottom < scrollBottom || this._physicalTop > scrollTop) { 3247 if (physicalBottom < scrollBottom || this._physicalTop > scrollTop) {
3186 this.async(this._increasePool.bind(this, 1)); 3248 this.async(this._increasePool.bind(this, 1));
3187 } 3249 }
3188 } else { 3250 } else {
3189 this._virtualStart = this._virtualStart + recycledTiles; 3251 this._virtualStart = this._virtualStart + recycledTiles;
3252 this._physicalStart = this._physicalStart + recycledTiles;
3190 this._update(recycledTileSet, movingUp); 3253 this._update(recycledTileSet, movingUp);
3191 } 3254 }
3192 }, 3255 },
3193 3256
3194 /** 3257 /**
3195 * Update the list of items, starting from the `_virtualStart` item. 3258 * Update the list of items, starting from the `_virtualStart` item.
3196 * @param {!Array<number>=} itemSet 3259 * @param {!Array<number>=} itemSet
3197 * @param {!Array<number>=} movingUp 3260 * @param {!Array<number>=} movingUp
3198 */ 3261 */
3199 _update: function(itemSet, movingUp) { 3262 _update: function(itemSet, movingUp) {
3200 // manage focus 3263 // manage focus
3201 if (this._isIndexRendered(this._focusedIndex)) { 3264 this._manageFocus();
3202 this._restoreFocusedItem();
3203 } else {
3204 this._createFocusBackfillItem();
3205 }
3206 // update models 3265 // update models
3207 this._assignModels(itemSet); 3266 this._assignModels(itemSet);
3208 // measure heights 3267 // measure heights
3209 this._updateMetrics(itemSet); 3268 this._updateMetrics(itemSet);
3210 // adjust offset after measuring 3269 // adjust offset after measuring
3211 if (movingUp) { 3270 if (movingUp) {
3212 while (movingUp.length) { 3271 while (movingUp.length) {
3213 this._physicalTop -= this._physicalSizes[movingUp.pop()]; 3272 this._physicalTop -= this._physicalSizes[movingUp.pop()];
3214 } 3273 }
3215 } 3274 }
(...skipping 46 matching lines...) Expand 10 before | Expand all | Expand 10 after
3262 this._debounceTemplate(this._increasePool.bind(this, 1)); 3321 this._debounceTemplate(this._increasePool.bind(this, 1));
3263 } 3322 }
3264 this._lastPage = currentPage; 3323 this._lastPage = currentPage;
3265 return true; 3324 return true;
3266 }, 3325 },
3267 3326
3268 /** 3327 /**
3269 * Increases the pool size. 3328 * Increases the pool size.
3270 */ 3329 */
3271 _increasePool: function(missingItems) { 3330 _increasePool: function(missingItems) {
3272 // limit the size
3273 var nextPhysicalCount = Math.min( 3331 var nextPhysicalCount = Math.min(
3274 this._physicalCount + missingItems, 3332 this._physicalCount + missingItems,
3275 this._virtualCount - this._virtualStart, 3333 this._virtualCount - this._virtualStart,
3276 MAX_PHYSICAL_COUNT 3334 MAX_PHYSICAL_COUNT
3277 ); 3335 );
3278 var prevPhysicalCount = this._physicalCount; 3336 var prevPhysicalCount = this._physicalCount;
3279 var delta = nextPhysicalCount - prevPhysicalCount; 3337 var delta = nextPhysicalCount - prevPhysicalCount;
3280 3338
3281 if (delta > 0) { 3339 if (delta <= 0) {
3282 [].push.apply(this._physicalItems, this._createPool(delta)); 3340 return;
3283 [].push.apply(this._physicalSizes, new Array(delta)); 3341 }
3284 3342
3285 this._physicalCount = prevPhysicalCount + delta; 3343 [].push.apply(this._physicalItems, this._createPool(delta));
3286 // tail call 3344 [].push.apply(this._physicalSizes, new Array(delta));
3287 return this._update(); 3345
3346 this._physicalCount = prevPhysicalCount + delta;
3347
3348 // update the physical start if we need to preserve the model of the focus ed item.
3349 // In this situation, the focused item is currently rendered and its model would
3350 // have changed after increasing the pool if the physical start remained u nchanged.
3351 if (this._physicalStart > this._physicalEnd &&
3352 this._isIndexRendered(this._focusedIndex) &&
3353 this._getPhysicalIndex(this._focusedIndex) < this._physicalEnd) {
3354 this._physicalStart = this._physicalStart + delta;
3288 } 3355 }
3356 this._update();
3289 }, 3357 },
3290 3358
3291 /** 3359 /**
3292 * Render a new list of items. This method does exactly the same as `update` , 3360 * Render a new list of items. This method does exactly the same as `update` ,
3293 * but it also ensures that only one `update` cycle is created. 3361 * but it also ensures that only one `update` cycle is created.
3294 */ 3362 */
3295 _render: function() { 3363 _render: function() {
3296 var requiresUpdate = this._virtualCount > 0 || this._physicalCount > 0; 3364 var requiresUpdate = this._virtualCount > 0 || this._physicalCount > 0;
3297 3365
3298 if (this.isAttached && !this._itemsRendered && this._isVisible && requires Update) { 3366 if (this.isAttached && !this._itemsRendered && this._isVisible && requires Update) {
(...skipping 68 matching lines...) Expand 10 before | Expand all | Expand 10 after
3367 _forwardParentPath: function(path, value) { 3435 _forwardParentPath: function(path, value) {
3368 if (this._physicalItems) { 3436 if (this._physicalItems) {
3369 this._physicalItems.forEach(function(item) { 3437 this._physicalItems.forEach(function(item) {
3370 item._templateInstance.notifyPath(path, value, true); 3438 item._templateInstance.notifyPath(path, value, true);
3371 }, this); 3439 }, this);
3372 } 3440 }
3373 }, 3441 },
3374 3442
3375 /** 3443 /**
3376 * Called as a side effect of a host items.<key>.<path> path change, 3444 * Called as a side effect of a host items.<key>.<path> path change,
3377 * responsible for notifying item.<path> changes to row for key. 3445 * responsible for notifying item.<path> changes.
3378 */ 3446 */
3379 _forwardItemPath: function(path, value) { 3447 _forwardItemPath: function(path, value) {
3380 if (this._physicalIndexForKey) { 3448 if (!this._physicalIndexForKey) {
3381 var dot = path.indexOf('.'); 3449 return;
3382 var key = path.substring(0, dot < 0 ? path.length : dot); 3450 }
3383 var idx = this._physicalIndexForKey[key]; 3451 var inst;
3384 var row = this._physicalItems[idx]; 3452 var dot = path.indexOf('.');
3453 var key = path.substring(0, dot < 0 ? path.length : dot);
3454 var idx = this._physicalIndexForKey[key];
3455 var el = this._physicalItems[idx];
3385 3456
3386 if (idx === this._focusedIndex && this._offscreenFocusedItem) { 3457
3387 row = this._offscreenFocusedItem; 3458 if (idx === this._focusedIndex && this._offscreenFocusedItem) {
3388 } 3459 el = this._offscreenFocusedItem;
3389 if (row) { 3460 }
3390 var inst = row._templateInstance; 3461 if (!el) {
3391 if (dot >= 0) { 3462 return;
3392 path = this.as + '.' + path.substring(dot+1); 3463 }
3393 inst.notifyPath(path, value, true); 3464
3394 } else { 3465 inst = el._templateInstance;
3395 inst[this.as] = value; 3466
3396 } 3467 if (inst.__key__ !== key) {
3397 } 3468 return;
3469 }
3470 if (dot >= 0) {
3471 path = this.as + '.' + path.substring(dot+1);
3472 inst.notifyPath(path, value, true);
3473 } else {
3474 inst[this.as] = value;
3398 } 3475 }
3399 }, 3476 },
3400 3477
3401 /** 3478 /**
3402 * Called when the items have changed. That is, ressignments 3479 * Called when the items have changed. That is, ressignments
3403 * to `items`, splices or updates to a single item. 3480 * to `items`, splices or updates to a single item.
3404 */ 3481 */
3405 _itemsChanged: function(change) { 3482 _itemsChanged: function(change) {
3406 if (change.path === 'items') { 3483 if (change.path === 'items') {
3407 3484 // reset items
3408 this._restoreFocusedItem();
3409 // render the new set
3410 this._itemsRendered = false;
3411 // update the whole set
3412 this._virtualStart = 0; 3485 this._virtualStart = 0;
3413 this._physicalTop = 0; 3486 this._physicalTop = 0;
3414 this._virtualCount = this.items ? this.items.length : 0; 3487 this._virtualCount = this.items ? this.items.length : 0;
3415 this._focusedIndex = 0;
3416 this._collection = this.items ? Polymer.Collection.get(this.items) : nul l; 3488 this._collection = this.items ? Polymer.Collection.get(this.items) : nul l;
3417 this._physicalIndexForKey = {}; 3489 this._physicalIndexForKey = {};
3418 3490
3419 this._resetScrollPosition(0); 3491 this._resetScrollPosition(0);
3492 this._removeFocusedItem();
3420 3493
3421 // create the initial physical items 3494 // create the initial physical items
3422 if (!this._physicalItems) { 3495 if (!this._physicalItems) {
3423 this._physicalCount = Math.max(1, Math.min(DEFAULT_PHYSICAL_COUNT, thi s._virtualCount)); 3496 this._physicalCount = Math.max(1, Math.min(DEFAULT_PHYSICAL_COUNT, thi s._virtualCount));
3424 this._physicalItems = this._createPool(this._physicalCount); 3497 this._physicalItems = this._createPool(this._physicalCount);
3425 this._physicalSizes = new Array(this._physicalCount); 3498 this._physicalSizes = new Array(this._physicalCount);
3426 } 3499 }
3427 this._debounceTemplate(this._render); 3500
3501 this._physicalStart = 0;
3428 3502
3429 } else if (change.path === 'items.splices') { 3503 } else if (change.path === 'items.splices') {
3430 // render the new set
3431 this._itemsRendered = false;
3432 this._adjustVirtualIndex(change.value.indexSplices); 3504 this._adjustVirtualIndex(change.value.indexSplices);
3433 this._virtualCount = this.items ? this.items.length : 0; 3505 this._virtualCount = this.items ? this.items.length : 0;
3434 3506
3435 this._debounceTemplate(this._render);
3436
3437 if (this._focusedIndex < 0 || this._focusedIndex >= this._virtualCount) {
3438 this._focusedIndex = 0;
3439 }
3440 this._debounceTemplate(this._render);
3441
3442 } else { 3507 } else {
3443 // update a single item 3508 // update a single item
3444 this._forwardItemPath(change.path.split('.').slice(1).join('.'), change. value); 3509 this._forwardItemPath(change.path.split('.').slice(1).join('.'), change. value);
3510 return;
3445 } 3511 }
3512
3513 this._itemsRendered = false;
3514 this._debounceTemplate(this._render);
3446 }, 3515 },
3447 3516
3448 /** 3517 /**
3449 * @param {!Array<!PolymerSplice>} splices 3518 * @param {!Array<!PolymerSplice>} splices
3450 */ 3519 */
3451 _adjustVirtualIndex: function(splices) { 3520 _adjustVirtualIndex: function(splices) {
3452 var i, splice, idx; 3521 splices.forEach(function(splice) {
3522 // deselect removed items
3523 splice.removed.forEach(this._removeItem, this);
3524 // We only need to care about changes happening above the current positi on
3525 if (splice.index < this._virtualStart) {
3526 var delta = Math.max(
3527 splice.addedCount - splice.removed.length,
3528 splice.index - this._virtualStart);
3453 3529
3454 for (i = 0; i < splices.length; i++) { 3530 this._virtualStart = this._virtualStart + delta;
3455 splice = splices[i];
3456 3531
3457 // deselect removed items 3532 if (this._focusedIndex >= 0) {
3458 splice.removed.forEach(this.$.selector.deselect, this.$.selector); 3533 this._focusedIndex = this._focusedIndex + delta;
3534 }
3535 }
3536 }, this);
3537 },
3459 3538
3460 idx = splice.index; 3539 _removeItem: function(item) {
3461 // We only need to care about changes happening above the current positi on 3540 this.$.selector.deselect(item);
3462 if (idx >= this._virtualStart) { 3541 // remove the current focused item
3463 break; 3542 if (this._focusedItem && this._focusedItem._templateInstance[this.as] === item) {
3464 } 3543 this._removeFocusedItem();
3465
3466 this._virtualStart = this._virtualStart +
3467 Math.max(splice.addedCount - splice.removed.length, idx - this._virt ualStart);
3468 } 3544 }
3469 }, 3545 },
3470 3546
3471 /** 3547 /**
3472 * Executes a provided function per every physical index in `itemSet` 3548 * Executes a provided function per every physical index in `itemSet`
3473 * `itemSet` default value is equivalent to the entire set of physical index es. 3549 * `itemSet` default value is equivalent to the entire set of physical index es.
3474 * 3550 *
3475 * @param {!function(number, number)} fn 3551 * @param {!function(number, number)} fn
3476 * @param {!Array<number>=} itemSet 3552 * @param {!Array<number>=} itemSet
3477 */ 3553 */
(...skipping 32 matching lines...) Expand 10 before | Expand all | Expand 10 after
3510 /** 3586 /**
3511 * Assigns the data models to a given set of items. 3587 * Assigns the data models to a given set of items.
3512 * @param {!Array<number>=} itemSet 3588 * @param {!Array<number>=} itemSet
3513 */ 3589 */
3514 _assignModels: function(itemSet) { 3590 _assignModels: function(itemSet) {
3515 this._iterateItems(function(pidx, vidx) { 3591 this._iterateItems(function(pidx, vidx) {
3516 var el = this._physicalItems[pidx]; 3592 var el = this._physicalItems[pidx];
3517 var inst = el._templateInstance; 3593 var inst = el._templateInstance;
3518 var item = this.items && this.items[vidx]; 3594 var item = this.items && this.items[vidx];
3519 3595
3520 if (item !== undefined && item !== null) { 3596 if (item != null) {
3521 inst[this.as] = item; 3597 inst[this.as] = item;
3522 inst.__key__ = this._collection.getKey(item); 3598 inst.__key__ = this._collection.getKey(item);
3523 inst[this.selectedAs] = /** @type {!ArraySelectorElement} */ (this.$.s elector).isSelected(item); 3599 inst[this.selectedAs] = /** @type {!ArraySelectorElement} */ (this.$.s elector).isSelected(item);
3524 inst[this.indexAs] = vidx; 3600 inst[this.indexAs] = vidx;
3525 inst.tabIndex = vidx === this._focusedIndex ? 0 : -1; 3601 inst.tabIndex = this._focusedIndex === vidx ? 0 : -1;
3602 this._physicalIndexForKey[inst.__key__] = pidx;
3526 el.removeAttribute('hidden'); 3603 el.removeAttribute('hidden');
3527 this._physicalIndexForKey[inst.__key__] = pidx;
3528 } else { 3604 } else {
3529 inst.__key__ = null; 3605 inst.__key__ = null;
3530 el.setAttribute('hidden', ''); 3606 el.setAttribute('hidden', '');
3531 } 3607 }
3532
3533 }, itemSet); 3608 }, itemSet);
3534 }, 3609 },
3535 3610
3536 /** 3611 /**
3537 * Updates the height for a given set of items. 3612 * Updates the height for a given set of items.
3538 * 3613 *
3539 * @param {!Array<number>=} itemSet 3614 * @param {!Array<number>=} itemSet
3540 */ 3615 */
3541 _updateMetrics: function(itemSet) { 3616 _updateMetrics: function(itemSet) {
3542 // Make sure we distributed all the physical items 3617 // Make sure we distributed all the physical items
(...skipping 27 matching lines...) Expand all
3570 3645
3571 /** 3646 /**
3572 * Updates the position of the physical items. 3647 * Updates the position of the physical items.
3573 */ 3648 */
3574 _positionItems: function() { 3649 _positionItems: function() {
3575 this._adjustScrollPosition(); 3650 this._adjustScrollPosition();
3576 3651
3577 var y = this._physicalTop; 3652 var y = this._physicalTop;
3578 3653
3579 this._iterateItems(function(pidx) { 3654 this._iterateItems(function(pidx) {
3580
3581 this.translate3d(0, y + 'px', 0, this._physicalItems[pidx]); 3655 this.translate3d(0, y + 'px', 0, this._physicalItems[pidx]);
3582 y += this._physicalSizes[pidx]; 3656 y += this._physicalSizes[pidx];
3583
3584 }); 3657 });
3585 }, 3658 },
3586 3659
3587 /** 3660 /**
3588 * Adjusts the scroll position when it was overestimated. 3661 * Adjusts the scroll position when it was overestimated.
3589 */ 3662 */
3590 _adjustScrollPosition: function() { 3663 _adjustScrollPosition: function() {
3591 var deltaHeight = this._virtualStart === 0 ? this._physicalTop : 3664 var deltaHeight = this._virtualStart === 0 ? this._physicalTop :
3592 Math.min(this._scrollPosition + this._physicalTop, 0); 3665 Math.min(this._scrollPosition + this._physicalTop, 0);
3593 3666
(...skipping 27 matching lines...) Expand all
3621 3694
3622 forceUpdate = forceUpdate || this._scrollHeight === 0; 3695 forceUpdate = forceUpdate || this._scrollHeight === 0;
3623 forceUpdate = forceUpdate || this._scrollPosition >= this._estScrollHeight - this._physicalSize; 3696 forceUpdate = forceUpdate || this._scrollPosition >= this._estScrollHeight - this._physicalSize;
3624 3697
3625 // amortize height adjustment, so it won't trigger repaints very often 3698 // amortize height adjustment, so it won't trigger repaints very often
3626 if (forceUpdate || Math.abs(this._estScrollHeight - this._scrollHeight) >= this._optPhysicalSize) { 3699 if (forceUpdate || Math.abs(this._estScrollHeight - this._scrollHeight) >= this._optPhysicalSize) {
3627 this.$.items.style.height = this._estScrollHeight + 'px'; 3700 this.$.items.style.height = this._estScrollHeight + 'px';
3628 this._scrollHeight = this._estScrollHeight; 3701 this._scrollHeight = this._estScrollHeight;
3629 } 3702 }
3630 }, 3703 },
3631
3632 /** 3704 /**
3633 * Scroll to a specific item in the virtual list regardless 3705 * Scroll to a specific item in the virtual list regardless
3634 * of the physical items in the DOM tree. 3706 * of the physical items in the DOM tree.
3635 * 3707 *
3636 * @method scrollToIndex 3708 * @method scrollToIndex
3637 * @param {number} idx The index of the item 3709 * @param {number} idx The index of the item
3638 */ 3710 */
3639 scrollToIndex: function(idx) { 3711 scrollToIndex: function(idx) {
3640 if (typeof idx !== 'number') { 3712 if (typeof idx !== 'number') {
3641 return; 3713 return;
3642 } 3714 }
3643 3715
3644 Polymer.dom.flush(); 3716 Polymer.dom.flush();
3645 3717
3646 var firstVisible = this.firstVisibleIndex;
3647 idx = Math.min(Math.max(idx, 0), this._virtualCount-1); 3718 idx = Math.min(Math.max(idx, 0), this._virtualCount-1);
3648 3719 // update the virtual start only when needed
3649 // start at the previous virtual item 3720 if (!this._isIndexRendered(idx) || idx >= this._maxVirtualStart) {
3650 // so we have a item above the first visible item 3721 this._virtualStart = idx - 1;
3651 this._virtualStart = idx - 1; 3722 }
3723 // manage focus
3724 this._manageFocus();
3652 // assign new models 3725 // assign new models
3653 this._assignModels(); 3726 this._assignModels();
3654 // measure the new sizes 3727 // measure the new sizes
3655 this._updateMetrics(); 3728 this._updateMetrics();
3656 // estimate new physical offset 3729 // estimate new physical offset
3657 this._physicalTop = this._virtualStart * this._physicalAverage; 3730 this._physicalTop = this._virtualStart * this._physicalAverage;
3658 3731
3659 var currentTopItem = this._physicalStart; 3732 var currentTopItem = this._physicalStart;
3660 var currentVirtualItem = this._virtualStart; 3733 var currentVirtualItem = this._virtualStart;
3661 var targetOffsetTop = 0; 3734 var targetOffsetTop = 0;
(...skipping 42 matching lines...) Expand 10 before | Expand all | Expand 10 after
3704 this.updateViewportBoundaries(); 3777 this.updateViewportBoundaries();
3705 this.scrollToIndex(this.firstVisibleIndex); 3778 this.scrollToIndex(this.firstVisibleIndex);
3706 } 3779 }
3707 }); 3780 });
3708 }, 3781 },
3709 3782
3710 _getModelFromItem: function(item) { 3783 _getModelFromItem: function(item) {
3711 var key = this._collection.getKey(item); 3784 var key = this._collection.getKey(item);
3712 var pidx = this._physicalIndexForKey[key]; 3785 var pidx = this._physicalIndexForKey[key];
3713 3786
3714 if (pidx !== undefined) { 3787 if (pidx != null) {
3715 return this._physicalItems[pidx]._templateInstance; 3788 return this._physicalItems[pidx]._templateInstance;
3716 } 3789 }
3717 return null; 3790 return null;
3718 }, 3791 },
3719 3792
3720 /** 3793 /**
3721 * Gets a valid item instance from its index or the object value. 3794 * Gets a valid item instance from its index or the object value.
3722 * 3795 *
3723 * @param {(Object|number)} item The item object or its index 3796 * @param {(Object|number)} item The item object or its index
3724 */ 3797 */
(...skipping 117 matching lines...) Expand 10 before | Expand all | Expand 10 after
3842 * Updates the size of an item. 3915 * Updates the size of an item.
3843 * 3916 *
3844 * @method updateSizeForItem 3917 * @method updateSizeForItem
3845 * @param {(Object|number)} item The item object or its index 3918 * @param {(Object|number)} item The item object or its index
3846 */ 3919 */
3847 updateSizeForItem: function(item) { 3920 updateSizeForItem: function(item) {
3848 item = this._getNormalizedItem(item); 3921 item = this._getNormalizedItem(item);
3849 var key = this._collection.getKey(item); 3922 var key = this._collection.getKey(item);
3850 var pidx = this._physicalIndexForKey[key]; 3923 var pidx = this._physicalIndexForKey[key];
3851 3924
3852 if (pidx !== undefined) { 3925 if (pidx != null) {
3853 this._updateMetrics([pidx]); 3926 this._updateMetrics([pidx]);
3854 this._positionItems(); 3927 this._positionItems();
3855 } 3928 }
3856 }, 3929 },
3857 3930
3931 /**
3932 * Creates a temporary backfill item in the rendered pool of physical items
3933 * to replace the main focused item. The focused item has tabIndex = 0
3934 * and might be currently focused by the user.
3935 *
3936 * This dynamic replacement helps to preserve the focus state.
3937 */
3938 _manageFocus: function() {
3939 var fidx = this._focusedIndex;
3940
3941 if (fidx >= 0 && fidx < this._virtualCount) {
3942 // if it's a valid index, check if that index is rendered
3943 // in a physical item.
3944 if (this._isIndexRendered(fidx)) {
3945 this._restoreFocusedItem();
3946 } else {
3947 this._createFocusBackfillItem();
3948 }
3949 } else if (this._virtualCount > 0 && this._physicalCount > 0) {
3950 // otherwise, assign the initial focused index.
3951 this._focusedIndex = this._virtualStart;
3952 this._focusedItem = this._physicalItems[this._physicalStart];
3953 }
3954 },
3955
3858 _isIndexRendered: function(idx) { 3956 _isIndexRendered: function(idx) {
3859 return idx >= this._virtualStart && idx <= this._virtualEnd; 3957 return idx >= this._virtualStart && idx <= this._virtualEnd;
3860 }, 3958 },
3861 3959
3862 _getPhysicalItemForIndex: function(idx, force) { 3960 _isIndexVisible: function(idx) {
3863 if (!this._collection) { 3961 return idx >= this.firstVisibleIndex && idx <= this.lastVisibleIndex;
3864 return null; 3962 },
3865 }
3866 if (!this._isIndexRendered(idx)) {
3867 if (force) {
3868 this.scrollToIndex(idx);
3869 return this._getPhysicalItemForIndex(idx, false);
3870 }
3871 return null;
3872 }
3873 var item = this._getNormalizedItem(idx);
3874 var physicalItem = this._physicalItems[this._physicalIndexForKey[this._col lection.getKey(item)]];
3875 3963
3876 return physicalItem || null; 3964 _getPhysicalIndex: function(idx) {
3965 return this._physicalIndexForKey[this._collection.getKey(this._getNormaliz edItem(idx))];
3877 }, 3966 },
3878 3967
3879 _focusPhysicalItem: function(idx) { 3968 _focusPhysicalItem: function(idx) {
3880 this._restoreFocusedItem(); 3969 if (idx < 0 || idx >= this._virtualCount) {
3881
3882 var physicalItem = this._getPhysicalItemForIndex(idx, true);
3883 if (!physicalItem) {
3884 return; 3970 return;
3885 } 3971 }
3972 this._restoreFocusedItem();
3973 // scroll to index to make sure it's rendered
3974 if (!this._isIndexRendered(idx)) {
3975 this.scrollToIndex(idx);
3976 }
3977
3978 var physicalItem = this._physicalItems[this._getPhysicalIndex(idx)];
3886 var SECRET = ~(Math.random() * 100); 3979 var SECRET = ~(Math.random() * 100);
3887 var model = physicalItem._templateInstance; 3980 var model = physicalItem._templateInstance;
3888 var focusable; 3981 var focusable;
3889 3982
3983 // set a secret tab index
3890 model.tabIndex = SECRET; 3984 model.tabIndex = SECRET;
3891 // the focusable element could be the entire physical item 3985 // check if focusable element is the physical item
3892 if (physicalItem.tabIndex === SECRET) { 3986 if (physicalItem.tabIndex === SECRET) {
3893 focusable = physicalItem; 3987 focusable = physicalItem;
3894 } 3988 }
3895 // the focusable element could be somewhere within the physical item 3989 // search for the element which tabindex is bound to the secret tab index
3896 if (!focusable) { 3990 if (!focusable) {
3897 focusable = Polymer.dom(physicalItem).querySelector('[tabindex="' + SECR ET + '"]'); 3991 focusable = Polymer.dom(physicalItem).querySelector('[tabindex="' + SECR ET + '"]');
3898 } 3992 }
3899 // restore the tab index 3993 // restore the tab index
3900 model.tabIndex = 0; 3994 model.tabIndex = 0;
3995 // focus the focusable element
3996 this._focusedIndex = idx;
3901 focusable && focusable.focus(); 3997 focusable && focusable.focus();
3902 }, 3998 },
3903 3999
3904 _restoreFocusedItem: function() { 4000 _removeFocusedItem: function() {
3905 if (!this._offscreenFocusedItem) { 4001 if (this._offscreenFocusedItem) {
3906 return; 4002 Polymer.dom(this).removeChild(this._offscreenFocusedItem);
3907 }
3908 var item = this._getNormalizedItem(this._focusedIndex);
3909 var pidx = this._physicalIndexForKey[this._collection.getKey(item)];
3910
3911 if (pidx !== undefined) {
3912 this.translate3d(0, HIDDEN_Y, 0, this._physicalItems[pidx]);
3913 this._physicalItems[pidx] = this._offscreenFocusedItem;
3914 } 4003 }
3915 this._offscreenFocusedItem = null; 4004 this._offscreenFocusedItem = null;
3916 },
3917
3918 _removeFocusedItem: function() {
3919 if (!this._offscreenFocusedItem) {
3920 return;
3921 }
3922 Polymer.dom(this).removeChild(this._offscreenFocusedItem);
3923 this._offscreenFocusedItem = null;
3924 this._focusBackfillItem = null; 4005 this._focusBackfillItem = null;
4006 this._focusedItem = null;
4007 this._focusedIndex = -1;
3925 }, 4008 },
3926 4009
3927 _createFocusBackfillItem: function() { 4010 _createFocusBackfillItem: function() {
3928 if (this._offscreenFocusedItem) { 4011 var pidx, fidx = this._focusedIndex;
4012 if (this._offscreenFocusedItem || fidx < 0) {
3929 return; 4013 return;
3930 } 4014 }
3931 var item = this._getNormalizedItem(this._focusedIndex);
3932 var pidx = this._physicalIndexForKey[this._collection.getKey(item)];
3933
3934 this._offscreenFocusedItem = this._physicalItems[pidx];
3935 this.translate3d(0, HIDDEN_Y, 0, this._offscreenFocusedItem);
3936
3937 if (!this._focusBackfillItem) { 4015 if (!this._focusBackfillItem) {
4016 // create a physical item, so that it backfills the focused item.
3938 var stampedTemplate = this.stamp(null); 4017 var stampedTemplate = this.stamp(null);
3939 this._focusBackfillItem = stampedTemplate.root.querySelector('*'); 4018 this._focusBackfillItem = stampedTemplate.root.querySelector('*');
3940 Polymer.dom(this).appendChild(stampedTemplate.root); 4019 Polymer.dom(this).appendChild(stampedTemplate.root);
3941 } 4020 }
3942 this._physicalItems[pidx] = this._focusBackfillItem; 4021 // get the physical index for the focused index
4022 pidx = this._getPhysicalIndex(fidx);
4023
4024 if (pidx != null) {
4025 // set the offcreen focused physical item
4026 this._offscreenFocusedItem = this._physicalItems[pidx];
4027 // backfill the focused physical item
4028 this._physicalItems[pidx] = this._focusBackfillItem;
4029 // hide the focused physical
4030 this.translate3d(0, HIDDEN_Y, 0, this._offscreenFocusedItem);
4031 }
4032 },
4033
4034 _restoreFocusedItem: function() {
4035 var pidx, fidx = this._focusedIndex;
4036
4037 if (!this._offscreenFocusedItem || this._focusedIndex < 0) {
4038 return;
4039 }
4040 // assign models to the focused index
4041 this._assignModels();
4042 // get the new physical index for the focused index
4043 pidx = this._getPhysicalIndex(fidx);
4044
4045 if (pidx != null) {
4046 // flip the focus backfill
4047 this._focusBackfillItem = this._physicalItems[pidx];
4048 // restore the focused physical item
4049 this._physicalItems[pidx] = this._offscreenFocusedItem;
4050 // reset the offscreen focused item
4051 this._offscreenFocusedItem = null;
4052 // hide the physical item that backfills
4053 this.translate3d(0, HIDDEN_Y, 0, this._focusBackfillItem);
4054 }
3943 }, 4055 },
3944 4056
3945 _didFocus: function(e) { 4057 _didFocus: function(e) {
3946 var targetModel = this.modelForElement(e.target); 4058 var targetModel = this.modelForElement(e.target);
4059 var focusedModel = this._focusedItem ? this._focusedItem._templateInstance : null;
4060 var hasOffscreenFocusedItem = this._offscreenFocusedItem !== null;
3947 var fidx = this._focusedIndex; 4061 var fidx = this._focusedIndex;
3948 4062
3949 if (!targetModel) { 4063 if (!targetModel || !focusedModel) {
3950 return; 4064 return;
3951 } 4065 }
3952 this._restoreFocusedItem(); 4066 if (focusedModel === targetModel) {
3953 4067 // if the user focused the same item, then bring it into view if it's no t visible
3954 if (this.modelForElement(this._offscreenFocusedItem) === targetModel) { 4068 if (!this._isIndexVisible(fidx)) {
3955 this.scrollToIndex(fidx); 4069 this.scrollToIndex(fidx);
4070 }
3956 } else { 4071 } else {
4072 this._restoreFocusedItem();
3957 // restore tabIndex for the currently focused item 4073 // restore tabIndex for the currently focused item
3958 this._getModelFromItem(this._getNormalizedItem(fidx)).tabIndex = -1; 4074 focusedModel.tabIndex = -1;
3959 // set the tabIndex for the next focused item 4075 // set the tabIndex for the next focused item
3960 targetModel.tabIndex = 0; 4076 targetModel.tabIndex = 0;
3961 fidx = /** @type {{index: number}} */(targetModel).index; 4077 fidx = targetModel[this.indexAs];
3962 this._focusedIndex = fidx; 4078 this._focusedIndex = fidx;
3963 // bring the item into view 4079 this._focusedItem = this._physicalItems[this._getPhysicalIndex(fidx)];
3964 if (fidx < this.firstVisibleIndex || fidx > this.lastVisibleIndex) { 4080
3965 this.scrollToIndex(fidx); 4081 if (hasOffscreenFocusedItem && !this._offscreenFocusedItem) {
3966 } else {
3967 this._update(); 4082 this._update();
3968 } 4083 }
3969 } 4084 }
3970 }, 4085 },
3971 4086
3972 _didMoveUp: function() { 4087 _didMoveUp: function() {
3973 this._focusPhysicalItem(Math.max(0, this._focusedIndex - 1)); 4088 this._focusPhysicalItem(this._focusedIndex - 1);
3974 }, 4089 },
3975 4090
3976 _didMoveDown: function() { 4091 _didMoveDown: function() {
3977 this._focusPhysicalItem(Math.min(this._virtualCount, this._focusedIndex + 1)); 4092 this._focusPhysicalItem(this._focusedIndex + 1);
3978 }, 4093 },
3979 4094
3980 _didEnter: function(e) { 4095 _didEnter: function(e) {
3981 // focus the currently focused physical item
3982 this._focusPhysicalItem(this._focusedIndex); 4096 this._focusPhysicalItem(this._focusedIndex);
3983 // toggle selection 4097 this._selectionHandler(e.detail.keyboardEvent);
3984 this._selectionHandler(/** @type {{keyboardEvent: Event}} */(e.detail).key boardEvent);
3985 } 4098 }
3986 }); 4099 });
3987 4100
3988 })(); 4101 })();
3989 (function() { 4102 (function() {
3990 4103
3991 // monostate data 4104 // monostate data
3992 var metaDatas = {}; 4105 var metaDatas = {};
3993 var metaArrays = {}; 4106 var metaArrays = {};
3994 var singleton = null; 4107 var singleton = null;
(...skipping 299 matching lines...) Expand 10 before | Expand all | Expand 10 after
4294 */ 4407 */
4295 src: { 4408 src: {
4296 type: String, 4409 type: String,
4297 observer: '_srcChanged' 4410 observer: '_srcChanged'
4298 }, 4411 },
4299 4412
4300 /** 4413 /**
4301 * @type {!Polymer.IronMeta} 4414 * @type {!Polymer.IronMeta}
4302 */ 4415 */
4303 _meta: { 4416 _meta: {
4304 value: Polymer.Base.create('iron-meta', {type: 'iconset'}) 4417 value: Polymer.Base.create('iron-meta', {type: 'iconset'}),
4418 observer: '_updateIcon'
4305 } 4419 }
4306 4420
4307 }, 4421 },
4308 4422
4309 _DEFAULT_ICONSET: 'icons', 4423 _DEFAULT_ICONSET: 'icons',
4310 4424
4311 _iconChanged: function(icon) { 4425 _iconChanged: function(icon) {
4312 var parts = (icon || '').split(':'); 4426 var parts = (icon || '').split(':');
4313 this._iconName = parts.pop(); 4427 this._iconName = parts.pop();
4314 this._iconsetName = parts.pop() || this._DEFAULT_ICONSET; 4428 this._iconsetName = parts.pop() || this._DEFAULT_ICONSET;
4315 this._updateIcon(); 4429 this._updateIcon();
4316 }, 4430 },
4317 4431
4318 _srcChanged: function(src) { 4432 _srcChanged: function(src) {
4319 this._updateIcon(); 4433 this._updateIcon();
4320 }, 4434 },
4321 4435
4322 _usesIconset: function() { 4436 _usesIconset: function() {
4323 return this.icon || !this.src; 4437 return this.icon || !this.src;
4324 }, 4438 },
4325 4439
4326 /** @suppress {visibility} */ 4440 /** @suppress {visibility} */
4327 _updateIcon: function() { 4441 _updateIcon: function() {
4328 if (this._usesIconset()) { 4442 if (this._usesIconset()) {
4329 if (this._iconsetName) { 4443 if (this._img && this._img.parentNode) {
4444 Polymer.dom(this.root).removeChild(this._img);
4445 }
4446 if (this._iconName === "") {
4447 if (this._iconset) {
4448 this._iconset.removeIcon(this);
4449 }
4450 } else if (this._iconsetName && this._meta) {
4330 this._iconset = /** @type {?Polymer.Iconset} */ ( 4451 this._iconset = /** @type {?Polymer.Iconset} */ (
4331 this._meta.byKey(this._iconsetName)); 4452 this._meta.byKey(this._iconsetName));
4332 if (this._iconset) { 4453 if (this._iconset) {
4333 this._iconset.applyIcon(this, this._iconName, this.theme); 4454 this._iconset.applyIcon(this, this._iconName, this.theme);
4334 this.unlisten(window, 'iron-iconset-added', '_updateIcon'); 4455 this.unlisten(window, 'iron-iconset-added', '_updateIcon');
4335 } else { 4456 } else {
4336 this.listen(window, 'iron-iconset-added', '_updateIcon'); 4457 this.listen(window, 'iron-iconset-added', '_updateIcon');
4337 } 4458 }
4338 } 4459 }
4339 } else { 4460 } else {
4461 if (this._iconset) {
4462 this._iconset.removeIcon(this);
4463 }
4340 if (!this._img) { 4464 if (!this._img) {
4341 this._img = document.createElement('img'); 4465 this._img = document.createElement('img');
4342 this._img.style.width = '100%'; 4466 this._img.style.width = '100%';
4343 this._img.style.height = '100%'; 4467 this._img.style.height = '100%';
4344 this._img.draggable = false; 4468 this._img.draggable = false;
4345 } 4469 }
4346 this._img.src = this.src; 4470 this._img.src = this.src;
4347 Polymer.dom(this.root).appendChild(this._img); 4471 Polymer.dom(this.root).appendChild(this._img);
4348 } 4472 }
4349 } 4473 }
(...skipping 248 matching lines...) Expand 10 before | Expand all | Expand 10 after
4598 } 4722 }
4599 }, 4723 },
4600 4724
4601 _disabledChanged: function(disabled, old) { 4725 _disabledChanged: function(disabled, old) {
4602 this.setAttribute('aria-disabled', disabled ? 'true' : 'false'); 4726 this.setAttribute('aria-disabled', disabled ? 'true' : 'false');
4603 this.style.pointerEvents = disabled ? 'none' : ''; 4727 this.style.pointerEvents = disabled ? 'none' : '';
4604 if (disabled) { 4728 if (disabled) {
4605 this._oldTabIndex = this.tabIndex; 4729 this._oldTabIndex = this.tabIndex;
4606 this.focused = false; 4730 this.focused = false;
4607 this.tabIndex = -1; 4731 this.tabIndex = -1;
4732 this.blur();
4608 } else if (this._oldTabIndex !== undefined) { 4733 } else if (this._oldTabIndex !== undefined) {
4609 this.tabIndex = this._oldTabIndex; 4734 this.tabIndex = this._oldTabIndex;
4610 } 4735 }
4611 }, 4736 },
4612 4737
4613 _changedControlState: function() { 4738 _changedControlState: function() {
4614 // _controlStateChanged is abstract, follow-on behaviors may implement it 4739 // _controlStateChanged is abstract, follow-on behaviors may implement it
4615 if (this._controlStateChanged) { 4740 if (this._controlStateChanged) {
4616 this._controlStateChanged(); 4741 this._controlStateChanged();
4617 } 4742 }
(...skipping 1710 matching lines...) Expand 10 before | Expand all | Expand 10 after
6328 6453
6329 /** 6454 /**
6330 * Sets the selection state for a given item to either selected or deselecte d. 6455 * Sets the selection state for a given item to either selected or deselecte d.
6331 * 6456 *
6332 * @method setItemSelected 6457 * @method setItemSelected
6333 * @param {*} item The item to select. 6458 * @param {*} item The item to select.
6334 * @param {boolean} isSelected True for selected, false for deselected. 6459 * @param {boolean} isSelected True for selected, false for deselected.
6335 */ 6460 */
6336 setItemSelected: function(item, isSelected) { 6461 setItemSelected: function(item, isSelected) {
6337 if (item != null) { 6462 if (item != null) {
6338 if (isSelected) { 6463 if (isSelected !== this.isSelected(item)) {
6339 this.selection.push(item); 6464 // proceed to update selection only if requested state differs from cu rrent
6340 } else { 6465 if (isSelected) {
6341 var i = this.selection.indexOf(item); 6466 this.selection.push(item);
6342 if (i >= 0) { 6467 } else {
6343 this.selection.splice(i, 1); 6468 var i = this.selection.indexOf(item);
6469 if (i >= 0) {
6470 this.selection.splice(i, 1);
6471 }
6344 } 6472 }
6345 } 6473 if (this.selectCallback) {
6346 if (this.selectCallback) { 6474 this.selectCallback(item, isSelected);
6347 this.selectCallback(item, isSelected); 6475 }
6348 } 6476 }
6349 } 6477 }
6350 }, 6478 },
6351 6479
6352 /** 6480 /**
6353 * Sets the selection state for a given item. If the `multi` property 6481 * Sets the selection state for a given item. If the `multi` property
6354 * is true, then the selected state of `item` will be toggled; otherwise 6482 * is true, then the selected state of `item` will be toggled; otherwise
6355 * the `item` will be selected. 6483 * the `item` will be selected.
6356 * 6484 *
6357 * @method select 6485 * @method select
(...skipping 136 matching lines...) Expand 10 before | Expand all | Expand 10 after
6494 type: Object, 6622 type: Object,
6495 value: function() { 6623 value: function() {
6496 return { 6624 return {
6497 'template': 1 6625 'template': 1
6498 }; 6626 };
6499 } 6627 }
6500 } 6628 }
6501 }, 6629 },
6502 6630
6503 observers: [ 6631 observers: [
6504 '_updateSelected(attrForSelected, selected)' 6632 '_updateAttrForSelected(attrForSelected)',
6633 '_updateSelected(selected)'
6505 ], 6634 ],
6506 6635
6507 created: function() { 6636 created: function() {
6508 this._bindFilterItem = this._filterItem.bind(this); 6637 this._bindFilterItem = this._filterItem.bind(this);
6509 this._selection = new Polymer.IronSelection(this._applySelection.bind(this )); 6638 this._selection = new Polymer.IronSelection(this._applySelection.bind(this ));
6510 }, 6639 },
6511 6640
6512 attached: function() { 6641 attached: function() {
6513 this._observer = this._observeItems(this); 6642 this._observer = this._observeItems(this);
6514 this._updateItems(); 6643 this._updateItems();
6515 if (!this._shouldUpdateSelection) { 6644 if (!this._shouldUpdateSelection) {
6516 this._updateSelected(this.attrForSelected,this.selected) 6645 this._updateSelected();
6517 } 6646 }
6518 this._addListener(this.activateEvent); 6647 this._addListener(this.activateEvent);
6519 }, 6648 },
6520 6649
6521 detached: function() { 6650 detached: function() {
6522 if (this._observer) { 6651 if (this._observer) {
6523 Polymer.dom(this).unobserveNodes(this._observer); 6652 Polymer.dom(this).unobserveNodes(this._observer);
6524 } 6653 }
6525 this._removeListener(this.activateEvent); 6654 this._removeListener(this.activateEvent);
6526 }, 6655 },
(...skipping 72 matching lines...) Expand 10 before | Expand all | Expand 10 after
6599 this._removeListener(old); 6728 this._removeListener(old);
6600 this._addListener(eventName); 6729 this._addListener(eventName);
6601 }, 6730 },
6602 6731
6603 _updateItems: function() { 6732 _updateItems: function() {
6604 var nodes = Polymer.dom(this).queryDistributedElements(this.selectable || '*'); 6733 var nodes = Polymer.dom(this).queryDistributedElements(this.selectable || '*');
6605 nodes = Array.prototype.filter.call(nodes, this._bindFilterItem); 6734 nodes = Array.prototype.filter.call(nodes, this._bindFilterItem);
6606 this._setItems(nodes); 6735 this._setItems(nodes);
6607 }, 6736 },
6608 6737
6738 _updateAttrForSelected: function() {
6739 if (this._shouldUpdateSelection) {
6740 this.selected = this._indexToValue(this.indexOf(this.selectedItem));
6741 }
6742 },
6743
6609 _updateSelected: function() { 6744 _updateSelected: function() {
6610 this._selectSelected(this.selected); 6745 this._selectSelected(this.selected);
6611 }, 6746 },
6612 6747
6613 _selectSelected: function(selected) { 6748 _selectSelected: function(selected) {
6614 this._selection.select(this._valueToItem(this.selected)); 6749 this._selection.select(this._valueToItem(this.selected));
6615 }, 6750 },
6616 6751
6617 _filterItem: function(node) { 6752 _filterItem: function(node) {
6618 return !this._excludedLocalNames[node.localName]; 6753 return !this._excludedLocalNames[node.localName];
(...skipping 49 matching lines...) Expand 10 before | Expand all | Expand 10 after
6668 // observe items change under the given node. 6803 // observe items change under the given node.
6669 _observeItems: function(node) { 6804 _observeItems: function(node) {
6670 return Polymer.dom(node).observeNodes(function(mutations) { 6805 return Polymer.dom(node).observeNodes(function(mutations) {
6671 this._updateItems(); 6806 this._updateItems();
6672 6807
6673 if (this._shouldUpdateSelection) { 6808 if (this._shouldUpdateSelection) {
6674 this._updateSelected(); 6809 this._updateSelected();
6675 } 6810 }
6676 6811
6677 // Let other interested parties know about the change so that 6812 // Let other interested parties know about the change so that
6678 // we don't have to recreate mutation observers everywher. 6813 // we don't have to recreate mutation observers everywhere.
6679 this.fire('iron-items-changed', mutations, { 6814 this.fire('iron-items-changed', mutations, {
6680 bubbles: false, 6815 bubbles: false,
6681 cancelable: false 6816 cancelable: false
6682 }); 6817 });
6683 }); 6818 });
6684 }, 6819 },
6685 6820
6686 _activateHandler: function(e) { 6821 _activateHandler: function(e) {
6687 var t = e.target; 6822 var t = e.target;
6688 var items = this.items; 6823 var items = this.items;
(...skipping 43 matching lines...) Expand 10 before | Expand all | Expand 10 after
6732 */ 6867 */
6733 selectedItems: { 6868 selectedItems: {
6734 type: Array, 6869 type: Array,
6735 readOnly: true, 6870 readOnly: true,
6736 notify: true 6871 notify: true
6737 }, 6872 },
6738 6873
6739 }, 6874 },
6740 6875
6741 observers: [ 6876 observers: [
6742 '_updateSelected(attrForSelected, selectedValues)' 6877 '_updateSelected(selectedValues)'
6743 ], 6878 ],
6744 6879
6745 /** 6880 /**
6746 * Selects the given value. If the `multi` property is true, then the select ed state of the 6881 * Selects the given value. If the `multi` property is true, then the select ed state of the
6747 * `value` will be toggled; otherwise the `value` will be selected. 6882 * `value` will be toggled; otherwise the `value` will be selected.
6748 * 6883 *
6749 * @method select 6884 * @method select
6750 * @param {string|number} value the value to select. 6885 * @param {string|number} value the value to select.
6751 */ 6886 */
6752 select: function(value) { 6887 select: function(value) {
(...skipping 10 matching lines...) Expand all
6763 6898
6764 multiChanged: function(multi) { 6899 multiChanged: function(multi) {
6765 this._selection.multi = multi; 6900 this._selection.multi = multi;
6766 }, 6901 },
6767 6902
6768 get _shouldUpdateSelection() { 6903 get _shouldUpdateSelection() {
6769 return this.selected != null || 6904 return this.selected != null ||
6770 (this.selectedValues != null && this.selectedValues.length); 6905 (this.selectedValues != null && this.selectedValues.length);
6771 }, 6906 },
6772 6907
6908 _updateAttrForSelected: function() {
6909 if (!this.multi) {
6910 Polymer.IronSelectableBehavior._updateAttrForSelected.apply(this);
6911 } else if (this._shouldUpdateSelection) {
6912 this.selectedValues = this.selectedItems.map(function(selectedItem) {
6913 return this._indexToValue(this.indexOf(selectedItem));
6914 }, this).filter(function(unfilteredValue) {
6915 return unfilteredValue != null;
6916 }, this);
6917 }
6918 },
6919
6773 _updateSelected: function() { 6920 _updateSelected: function() {
6774 if (this.multi) { 6921 if (this.multi) {
6775 this._selectMulti(this.selectedValues); 6922 this._selectMulti(this.selectedValues);
6776 } else { 6923 } else {
6777 this._selectSelected(this.selected); 6924 this._selectSelected(this.selected);
6778 } 6925 }
6779 }, 6926 },
6780 6927
6781 _selectMulti: function(values) { 6928 _selectMulti: function(values) {
6782 this._selection.clear();
6783 if (values) { 6929 if (values) {
6784 for (var i = 0; i < values.length; i++) { 6930 var selectedItems = this._valuesToItems(values);
6785 this._selection.setItemSelected(this._valueToItem(values[i]), true); 6931 // clear all but the current selected items
6932 this._selection.clear(selectedItems);
6933 // select only those not selected yet
6934 for (var i = 0; i < selectedItems.length; i++) {
6935 this._selection.setItemSelected(selectedItems[i], true);
6786 } 6936 }
6937 } else {
6938 this._selection.clear();
6787 } 6939 }
6788 }, 6940 },
6789 6941
6790 _selectionChange: function() { 6942 _selectionChange: function() {
6791 var s = this._selection.get(); 6943 var s = this._selection.get();
6792 if (this.multi) { 6944 if (this.multi) {
6793 this._setSelectedItems(s); 6945 this._setSelectedItems(s);
6794 } else { 6946 } else {
6795 this._setSelectedItems([s]); 6947 this._setSelectedItems([s]);
6796 this._setSelectedItem(s); 6948 this._setSelectedItem(s);
6797 } 6949 }
6798 }, 6950 },
6799 6951
6800 _toggleSelected: function(value) { 6952 _toggleSelected: function(value) {
6801 var i = this.selectedValues.indexOf(value); 6953 var i = this.selectedValues.indexOf(value);
6802 var unselected = i < 0; 6954 var unselected = i < 0;
6803 if (unselected) { 6955 if (unselected) {
6804 this.push('selectedValues',value); 6956 this.push('selectedValues',value);
6805 } else { 6957 } else {
6806 this.splice('selectedValues',i,1); 6958 this.splice('selectedValues',i,1);
6807 } 6959 }
6808 this._selection.setItemSelected(this._valueToItem(value), unselected); 6960 this._selection.setItemSelected(this._valueToItem(value), unselected);
6961 },
6962
6963 _valuesToItems: function(values) {
6964 return (values == null) ? null : values.map(function(value) {
6965 return this._valueToItem(value);
6966 }, this);
6809 } 6967 }
6810 }; 6968 };
6811 6969
6812 /** @polymerBehavior */ 6970 /** @polymerBehavior */
6813 Polymer.IronMultiSelectableBehavior = [ 6971 Polymer.IronMultiSelectableBehavior = [
6814 Polymer.IronSelectableBehavior, 6972 Polymer.IronSelectableBehavior,
6815 Polymer.IronMultiSelectableBehaviorImpl 6973 Polymer.IronMultiSelectableBehaviorImpl
6816 ]; 6974 ];
6817 /** 6975 /**
6818 * `Polymer.IronMenuBehavior` implements accessible menu behavior. 6976 * `Polymer.IronMenuBehavior` implements accessible menu behavior.
(...skipping 213 matching lines...) Expand 10 before | Expand all | Expand 10 after
7032 * Handler that is called when the menu receives focus. 7190 * Handler that is called when the menu receives focus.
7033 * 7191 *
7034 * @param {FocusEvent} event A focus event. 7192 * @param {FocusEvent} event A focus event.
7035 */ 7193 */
7036 _onFocus: function(event) { 7194 _onFocus: function(event) {
7037 if (Polymer.IronMenuBehaviorImpl._shiftTabPressed) { 7195 if (Polymer.IronMenuBehaviorImpl._shiftTabPressed) {
7038 // do not focus the menu itself 7196 // do not focus the menu itself
7039 return; 7197 return;
7040 } 7198 }
7041 7199
7200 // Do not focus the selected tab if the deepest target is part of the
7201 // menu element's local DOM and is focusable.
7202 var rootTarget = /** @type {?HTMLElement} */(
7203 Polymer.dom(event).rootTarget);
7204 if (rootTarget !== this && typeof rootTarget.tabIndex !== "undefined" && ! this.isLightDescendant(rootTarget)) {
7205 return;
7206 }
7207
7042 this.blur(); 7208 this.blur();
7043 7209
7044 // clear the cached focus item 7210 // clear the cached focus item
7045 this._defaultFocusAsync = this.async(function() { 7211 this._defaultFocusAsync = this.async(function() {
7046 // focus the selected item when the menu receives focus, or the first it em 7212 // focus the selected item when the menu receives focus, or the first it em
7047 // if no item is selected 7213 // if no item is selected
7048 var selectedItem = this.multi ? (this.selectedItems && this.selectedItem s[0]) : this.selectedItem; 7214 var selectedItem = this.multi ? (this.selectedItems && this.selectedItem s[0]) : this.selectedItem;
7049 7215
7050 this._setFocusedItem(null); 7216 this._setFocusedItem(null);
7051 7217
7052 if (selectedItem) { 7218 if (selectedItem) {
7053 this._setFocusedItem(selectedItem); 7219 this._setFocusedItem(selectedItem);
7054 } else { 7220 } else {
7055 this._setFocusedItem(this.items[0]); 7221 this._setFocusedItem(this.items[0]);
7056 } 7222 }
7057 // async 1ms to wait for `select` to get called from `_itemActivate` 7223 // async 1ms to wait for `select` to get called from `_itemActivate`
7058 }, 1); 7224 }, 1);
7059 }, 7225 },
7060 7226
7061 /** 7227 /**
7062 * Handler that is called when the up key is pressed. 7228 * Handler that is called when the up key is pressed.
7063 * 7229 *
7064 * @param {CustomEvent} event A key combination event. 7230 * @param {CustomEvent} event A key combination event.
7065 */ 7231 */
7066 _onUpKey: function(event) { 7232 _onUpKey: function(event) {
7067 // up and down arrows moves the focus 7233 // up and down arrows moves the focus
7068 this._focusPrevious(); 7234 this._focusPrevious();
7235 event.detail.keyboardEvent.preventDefault();
7069 }, 7236 },
7070 7237
7071 /** 7238 /**
7072 * Handler that is called when the down key is pressed. 7239 * Handler that is called when the down key is pressed.
7073 * 7240 *
7074 * @param {CustomEvent} event A key combination event. 7241 * @param {CustomEvent} event A key combination event.
7075 */ 7242 */
7076 _onDownKey: function(event) { 7243 _onDownKey: function(event) {
7077 this._focusNext(); 7244 this._focusNext();
7245 event.detail.keyboardEvent.preventDefault();
7078 }, 7246 },
7079 7247
7080 /** 7248 /**
7081 * Handler that is called when the esc key is pressed. 7249 * Handler that is called when the esc key is pressed.
7082 * 7250 *
7083 * @param {CustomEvent} event A key combination event. 7251 * @param {CustomEvent} event A key combination event.
7084 */ 7252 */
7085 _onEscKey: function(event) { 7253 _onEscKey: function(event) {
7086 // esc blurs the control 7254 // esc blurs the control
7087 this.focusedItem.blur(); 7255 this.focusedItem.blur();
(...skipping 188 matching lines...) Expand 10 before | Expand all | Expand 10 after
7276 left: parseInt(target.marginLeft, 10) || 0 7444 left: parseInt(target.marginLeft, 10) || 0
7277 } 7445 }
7278 }; 7446 };
7279 }, 7447 },
7280 7448
7281 /** 7449 /**
7282 * Resets the target element's position and size constraints, and clear 7450 * Resets the target element's position and size constraints, and clear
7283 * the memoized data. 7451 * the memoized data.
7284 */ 7452 */
7285 resetFit: function() { 7453 resetFit: function() {
7454 if (!this._fitInfo || !this._fitInfo.sizedBy.width) {
7455 this.sizingTarget.style.maxWidth = '';
7456 }
7286 if (!this._fitInfo || !this._fitInfo.sizedBy.height) { 7457 if (!this._fitInfo || !this._fitInfo.sizedBy.height) {
7287 this.sizingTarget.style.maxHeight = ''; 7458 this.sizingTarget.style.maxHeight = '';
7288 this.style.top = this._fitInfo ? this._fitInfo.inlineStyle.top : '';
7289 } 7459 }
7290 if (!this._fitInfo || !this._fitInfo.sizedBy.width) { 7460 this.style.top = this._fitInfo ? this._fitInfo.inlineStyle.top : '';
7291 this.sizingTarget.style.maxWidth = ''; 7461 this.style.left = this._fitInfo ? this._fitInfo.inlineStyle.left : '';
7292 this.style.left = this._fitInfo ? this._fitInfo.inlineStyle.left : '';
7293 }
7294 if (this._fitInfo) { 7462 if (this._fitInfo) {
7295 this.style.position = this._fitInfo.positionedBy.css; 7463 this.style.position = this._fitInfo.positionedBy.css;
7296 } 7464 }
7297 this._fitInfo = null; 7465 this._fitInfo = null;
7298 }, 7466 },
7299 7467
7300 /** 7468 /**
7301 * Equivalent to calling `resetFit()` and `fit()`. Useful to call this after the element, 7469 * Equivalent to calling `resetFit()` and `fit()`. Useful to call this after the element,
7302 * the window, or the `fitInfo` element has been resized. 7470 * the window, or the `fitInfo` element has been resized.
7303 */ 7471 */
(...skipping 40 matching lines...) Expand 10 before | Expand all | Expand 10 after
7344 var offsetExtent = 'offset' + extent; 7512 var offsetExtent = 'offset' + extent;
7345 var sizingOffset = this[offsetExtent] - this.sizingTarget[offsetExtent]; 7513 var sizingOffset = this[offsetExtent] - this.sizingTarget[offsetExtent];
7346 this.sizingTarget.style['max' + extent] = (max - margin - offset - sizingO ffset) + 'px'; 7514 this.sizingTarget.style['max' + extent] = (max - margin - offset - sizingO ffset) + 'px';
7347 }, 7515 },
7348 7516
7349 /** 7517 /**
7350 * Centers horizontally and vertically if not already positioned. This also sets 7518 * Centers horizontally and vertically if not already positioned. This also sets
7351 * `position:fixed`. 7519 * `position:fixed`.
7352 */ 7520 */
7353 center: function() { 7521 center: function() {
7354 if (!this._fitInfo.positionedBy.vertically || !this._fitInfo.positionedBy. horizontally) { 7522 var positionedBy = this._fitInfo.positionedBy;
7355 // need position:fixed to center 7523 if (positionedBy.vertically && positionedBy.horizontally) {
7356 this.style.position = 'fixed'; 7524 // Already positioned.
7525 return;
7357 } 7526 }
7358 if (!this._fitInfo.positionedBy.vertically) { 7527 // Need position:fixed to center
7359 var top = (this._fitHeight - this.offsetHeight) / 2 + this._fitTop; 7528 this.style.position = 'fixed';
7360 top -= this._fitInfo.margin.top; 7529 // Take into account the offset caused by parents that create stacking
7530 // contexts (e.g. with transform: translate3d). Translate to 0,0 and
7531 // measure the bounding rect.
7532 if (!positionedBy.vertically) {
7533 this.style.top = '0px';
7534 }
7535 if (!positionedBy.horizontally) {
7536 this.style.left = '0px';
7537 }
7538 // It will take in consideration margins and transforms
7539 var rect = this.getBoundingClientRect();
7540 if (!positionedBy.vertically) {
7541 var top = this._fitTop - rect.top + (this._fitHeight - rect.height) / 2;
7361 this.style.top = top + 'px'; 7542 this.style.top = top + 'px';
7362 } 7543 }
7363 if (!this._fitInfo.positionedBy.horizontally) { 7544 if (!positionedBy.horizontally) {
7364 var left = (this._fitWidth - this.offsetWidth) / 2 + this._fitLeft; 7545 var left = this._fitLeft - rect.left + (this._fitWidth - rect.width) / 2 ;
7365 left -= this._fitInfo.margin.left;
7366 this.style.left = left + 'px'; 7546 this.style.left = left + 'px';
7367 } 7547 }
7368 } 7548 }
7369 7549
7370 }; 7550 };
7371 /** 7551 /**
7372 * @struct 7552 * @struct
7373 * @constructor 7553 * @constructor
7374 */ 7554 */
7375 Polymer.IronOverlayManagerClass = function() { 7555 Polymer.IronOverlayManagerClass = function() {
7376 this._overlays = []; 7556 this._overlays = [];
7557 // Used to keep track of the last focused node before an overlay gets opened .
7558 this._lastFocusedNodes = [];
7559
7377 /** 7560 /**
7378 * iframes have a default z-index of 100, so this default should be at least 7561 * iframes have a default z-index of 100, so this default should be at least
7379 * that. 7562 * that.
7380 * @private {number} 7563 * @private {number}
7381 */ 7564 */
7382 this._minimumZ = 101; 7565 this._minimumZ = 101;
7383 7566
7384 this._backdrops = []; 7567 this._backdrops = [];
7385 } 7568
7569 this._backdropElement = null;
7570 Object.defineProperty(this, 'backdropElement', {
7571 get: function() {
7572 if (!this._backdropElement) {
7573 this._backdropElement = document.createElement('iron-overlay-backdrop' );
7574 }
7575 return this._backdropElement;
7576 }.bind(this)
7577 });
7578
7579 /**
7580 * The deepest active element.
7581 * returns {?Node} element the active element
7582 */
7583 this.deepActiveElement = null;
7584 Object.defineProperty(this, 'deepActiveElement', {
7585 get: function() {
7586 var active = document.activeElement;
7587 // document.activeElement can be null
7588 // https://developer.mozilla.org/en-US/docs/Web/API/Document/activeEleme nt
7589 while (active && active.root && Polymer.dom(active.root).activeElement) {
7590 active = Polymer.dom(active.root).activeElement;
7591 }
7592 return active;
7593 }.bind(this)
7594 });
7595 };
7596
7597 /**
7598 * If a node is contained in an overlay.
7599 * @private
7600 * @param {Node} node
7601 * @returns {Boolean}
7602 */
7603 Polymer.IronOverlayManagerClass.prototype._isChildOfOverlay = function(node) {
7604 while (node && node !== document.body) {
7605 // Use logical parentNode, or native ShadowRoot host.
7606 node = Polymer.dom(node).parentNode || node.host;
7607 // Check if it is an overlay.
7608 if (node && node.behaviors && node.behaviors.indexOf(Polymer.IronOverlayBe haviorImpl) !== -1) {
7609 return true;
7610 }
7611 }
7612 return false;
7613 };
7386 7614
7387 Polymer.IronOverlayManagerClass.prototype._applyOverlayZ = function(overlay, a boveZ) { 7615 Polymer.IronOverlayManagerClass.prototype._applyOverlayZ = function(overlay, a boveZ) {
7388 this._setZ(overlay, aboveZ + 2); 7616 this._setZ(overlay, aboveZ + 2);
7389 }; 7617 };
7390 7618
7391 Polymer.IronOverlayManagerClass.prototype._setZ = function(element, z) { 7619 Polymer.IronOverlayManagerClass.prototype._setZ = function(element, z) {
7392 element.style.zIndex = z; 7620 element.style.zIndex = z;
7393 }; 7621 };
7394 7622
7395 /** 7623 /**
7396 * track overlays for z-index and focus managemant 7624 * track overlays for z-index and focus managemant
7397 */ 7625 */
7398 Polymer.IronOverlayManagerClass.prototype.addOverlay = function(overlay) { 7626 Polymer.IronOverlayManagerClass.prototype.addOverlay = function(overlay) {
7399 var minimumZ = Math.max(this.currentOverlayZ(), this._minimumZ); 7627 var minimumZ = Math.max(this.currentOverlayZ(), this._minimumZ);
7400 this._overlays.push(overlay); 7628 this._overlays.push(overlay);
7401 var newZ = this.currentOverlayZ(); 7629 var newZ = this.currentOverlayZ();
7402 if (newZ <= minimumZ) { 7630 if (newZ <= minimumZ) {
7403 this._applyOverlayZ(overlay, minimumZ); 7631 this._applyOverlayZ(overlay, minimumZ);
7404 } 7632 }
7633 var element = this.deepActiveElement;
7634 // If already in other overlay, don't reset focus there.
7635 if (this._isChildOfOverlay(element)) {
7636 element = null;
7637 }
7638 this._lastFocusedNodes.push(element);
7405 }; 7639 };
7406 7640
7407 Polymer.IronOverlayManagerClass.prototype.removeOverlay = function(overlay) { 7641 Polymer.IronOverlayManagerClass.prototype.removeOverlay = function(overlay) {
7408 var i = this._overlays.indexOf(overlay); 7642 var i = this._overlays.indexOf(overlay);
7409 if (i >= 0) { 7643 if (i >= 0) {
7410 this._overlays.splice(i, 1); 7644 this._overlays.splice(i, 1);
7411 this._setZ(overlay, ''); 7645 this._setZ(overlay, '');
7646
7647 var node = this._lastFocusedNodes[i];
7648 // Focus only if still contained in document.body
7649 if (overlay.restoreFocusOnClose && node && Polymer.dom(document.body).deep Contains(node)) {
7650 node.focus();
7651 }
7652 this._lastFocusedNodes.splice(i, 1);
7412 } 7653 }
7413 }; 7654 };
7414 7655
7415 Polymer.IronOverlayManagerClass.prototype.currentOverlay = function() { 7656 Polymer.IronOverlayManagerClass.prototype.currentOverlay = function() {
7416 var i = this._overlays.length - 1; 7657 var i = this._overlays.length - 1;
7417 while (this._overlays[i] && !this._overlays[i].opened) { 7658 while (this._overlays[i] && !this._overlays[i].opened) {
7418 --i; 7659 --i;
7419 } 7660 }
7420 return this._overlays[i]; 7661 return this._overlays[i];
7421 }; 7662 };
(...skipping 34 matching lines...) Expand 10 before | Expand all | Expand 10 after
7456 if (element.opened && element.withBackdrop) { 7697 if (element.opened && element.withBackdrop) {
7457 // no duplicates 7698 // no duplicates
7458 if (index === -1) { 7699 if (index === -1) {
7459 this._backdrops.push(element); 7700 this._backdrops.push(element);
7460 } 7701 }
7461 } else if (index >= 0) { 7702 } else if (index >= 0) {
7462 this._backdrops.splice(index, 1); 7703 this._backdrops.splice(index, 1);
7463 } 7704 }
7464 }; 7705 };
7465 7706
7466 Object.defineProperty(Polymer.IronOverlayManagerClass.prototype, "backdropElem ent", {
7467 get: function() {
7468 if (!this._backdropElement) {
7469 this._backdropElement = document.createElement('iron-overlay-backdrop');
7470 }
7471 return this._backdropElement;
7472 }
7473 });
7474
7475 Polymer.IronOverlayManagerClass.prototype.getBackdrops = function() { 7707 Polymer.IronOverlayManagerClass.prototype.getBackdrops = function() {
7476 return this._backdrops; 7708 return this._backdrops;
7477 }; 7709 };
7478 7710
7479 /** 7711 /**
7480 * Returns the z-index for the backdrop. 7712 * Returns the z-index for the backdrop.
7481 */ 7713 */
7482 Polymer.IronOverlayManagerClass.prototype.backdropZ = function() { 7714 Polymer.IronOverlayManagerClass.prototype.backdropZ = function() {
7483 return this._getOverlayZ(this._overlayWithBackdrop()) - 1; 7715 return this._getOverlayZ(this._overlayWithBackdrop()) - 1;
7484 }; 7716 };
(...skipping 209 matching lines...) Expand 10 before | Expand all | Expand 10 after
7694 7926
7695 /** 7927 /**
7696 * Returns the reason this dialog was last closed. 7928 * Returns the reason this dialog was last closed.
7697 */ 7929 */
7698 closingReason: { 7930 closingReason: {
7699 // was a getter before, but needs to be a property so other 7931 // was a getter before, but needs to be a property so other
7700 // behaviors can override this. 7932 // behaviors can override this.
7701 type: Object 7933 type: Object
7702 }, 7934 },
7703 7935
7936 /**
7937 * The HTMLElement that will be firing relevant KeyboardEvents.
7938 * Used for capturing esc and tab. Overridden from `IronA11yKeysBehavior`.
7939 */
7940 keyEventTarget: {
7941 type: Object,
7942 value: document
7943 },
7944
7945 /**
7946 * Set to true to enable restoring of focus when overlay is closed.
7947 */
7948 restoreFocusOnClose: {
7949 type: Boolean,
7950 value: false
7951 },
7952
7704 _manager: { 7953 _manager: {
7705 type: Object, 7954 type: Object,
7706 value: Polymer.IronOverlayManager 7955 value: Polymer.IronOverlayManager
7707 }, 7956 },
7708 7957
7709 _boundOnCaptureClick: { 7958 _boundOnCaptureClick: {
7710 type: Function, 7959 type: Function,
7711 value: function() { 7960 value: function() {
7712 return this._onCaptureClick.bind(this); 7961 return this._onCaptureClick.bind(this);
7713 } 7962 }
7714 }, 7963 },
7715 7964
7716 _boundOnCaptureKeydown: {
7717 type: Function,
7718 value: function() {
7719 return this._onCaptureKeydown.bind(this);
7720 }
7721 },
7722
7723 _boundOnCaptureFocus: { 7965 _boundOnCaptureFocus: {
7724 type: Function, 7966 type: Function,
7725 value: function() { 7967 value: function() {
7726 return this._onCaptureFocus.bind(this); 7968 return this._onCaptureFocus.bind(this);
7727 } 7969 }
7728 }, 7970 },
7729 7971
7730 /** @type {?Node} */ 7972 /**
7973 * The node being focused.
7974 * @type {?Node}
7975 */
7731 _focusedChild: { 7976 _focusedChild: {
7732 type: Object 7977 type: Object
7733 } 7978 }
7734 7979
7735 }, 7980 },
7736 7981
7982 keyBindings: {
7983 'esc': '__onEsc',
7984 'tab': '__onTab'
7985 },
7986
7737 listeners: { 7987 listeners: {
7738 'iron-resize': '_onIronResize' 7988 'iron-resize': '_onIronResize'
7739 }, 7989 },
7740 7990
7741 /** 7991 /**
7742 * The backdrop element. 7992 * The backdrop element.
7743 * @type Node 7993 * @type {Node}
7744 */ 7994 */
7745 get backdropElement() { 7995 get backdropElement() {
7746 return this._manager.backdropElement; 7996 return this._manager.backdropElement;
7747 }, 7997 },
7748 7998
7999 /**
8000 * Returns the node to give focus to.
8001 * @type {Node}
8002 */
7749 get _focusNode() { 8003 get _focusNode() {
7750 return this._focusedChild || Polymer.dom(this).querySelector('[autofocus]' ) || this; 8004 return this._focusedChild || Polymer.dom(this).querySelector('[autofocus]' ) || this;
7751 }, 8005 },
7752 8006
8007 /**
8008 * Array of nodes that can receive focus (overlay included), ordered by `tab index`.
8009 * This is used to retrieve which is the first and last focusable nodes in o rder
8010 * to wrap the focus for overlays `with-backdrop`.
8011 *
8012 * If you know what is your content (specifically the first and last focusab le children),
8013 * you can override this method to return only `[firstFocusable, lastFocusab le];`
8014 * @type {[Node]}
8015 * @protected
8016 */
8017 get _focusableNodes() {
8018 // Elements that can be focused even if they have [disabled] attribute.
8019 var FOCUSABLE_WITH_DISABLED = [
8020 'a[href]',
8021 'area[href]',
8022 'iframe',
8023 '[tabindex]',
8024 '[contentEditable=true]'
8025 ];
8026
8027 // Elements that cannot be focused if they have [disabled] attribute.
8028 var FOCUSABLE_WITHOUT_DISABLED = [
8029 'input',
8030 'select',
8031 'textarea',
8032 'button'
8033 ];
8034
8035 // Discard elements with tabindex=-1 (makes them not focusable).
8036 var selector = FOCUSABLE_WITH_DISABLED.join(':not([tabindex="-1"]),') +
8037 ':not([tabindex="-1"]),' +
8038 FOCUSABLE_WITHOUT_DISABLED.join(':not([disabled]):not([tabindex="-1"]),' ) +
8039 ':not([disabled]):not([tabindex="-1"])';
8040
8041 var focusables = Polymer.dom(this).querySelectorAll(selector);
8042 if (this.tabIndex >= 0) {
8043 // Insert at the beginning because we might have all elements with tabIn dex = 0,
8044 // and the overlay should be the first of the list.
8045 focusables.splice(0, 0, this);
8046 }
8047 // Sort by tabindex.
8048 return focusables.sort(function (a, b) {
8049 if (a.tabIndex === b.tabIndex) {
8050 return 0;
8051 }
8052 if (a.tabIndex === 0 || a.tabIndex > b.tabIndex) {
8053 return 1;
8054 }
8055 return -1;
8056 });
8057 },
8058
7753 ready: function() { 8059 ready: function() {
7754 // with-backdrop need tabindex to be set in order to trap the focus. 8060 // with-backdrop needs tabindex to be set in order to trap the focus.
7755 // If it is not set, IronOverlayBehavior will set it, and remove it if wit h-backdrop = false. 8061 // If it is not set, IronOverlayBehavior will set it, and remove it if wit h-backdrop = false.
7756 this.__shouldRemoveTabIndex = false; 8062 this.__shouldRemoveTabIndex = false;
8063 // Used for wrapping the focus on TAB / Shift+TAB.
8064 this.__firstFocusableNode = this.__lastFocusableNode = null;
7757 this._ensureSetup(); 8065 this._ensureSetup();
7758 }, 8066 },
7759 8067
7760 attached: function() { 8068 attached: function() {
7761 // Call _openedChanged here so that position can be computed correctly. 8069 // Call _openedChanged here so that position can be computed correctly.
7762 if (this._callOpenedWhenReady) { 8070 if (this.opened) {
7763 this._openedChanged(); 8071 this._openedChanged();
7764 } 8072 }
8073 this._observer = Polymer.dom(this).observeNodes(this._onNodesChange);
7765 }, 8074 },
7766 8075
7767 detached: function() { 8076 detached: function() {
8077 Polymer.dom(this).unobserveNodes(this._observer);
8078 this._observer = null;
7768 this.opened = false; 8079 this.opened = false;
7769 this._manager.trackBackdrop(this); 8080 this._manager.trackBackdrop(this);
7770 this._manager.removeOverlay(this); 8081 this._manager.removeOverlay(this);
7771 }, 8082 },
7772 8083
7773 /** 8084 /**
7774 * Toggle the opened state of the overlay. 8085 * Toggle the opened state of the overlay.
7775 */ 8086 */
7776 toggle: function() { 8087 toggle: function() {
7777 this._setCanceled(false); 8088 this._setCanceled(false);
(...skipping 11 matching lines...) Expand all
7789 /** 8100 /**
7790 * Close the overlay. 8101 * Close the overlay.
7791 */ 8102 */
7792 close: function() { 8103 close: function() {
7793 this._setCanceled(false); 8104 this._setCanceled(false);
7794 this.opened = false; 8105 this.opened = false;
7795 }, 8106 },
7796 8107
7797 /** 8108 /**
7798 * Cancels the overlay. 8109 * Cancels the overlay.
8110 * @param {?Event} event The original event
7799 */ 8111 */
7800 cancel: function() { 8112 cancel: function(event) {
7801 var cancelEvent = this.fire('iron-overlay-canceled', undefined, {cancelabl e: true}); 8113 var cancelEvent = this.fire('iron-overlay-canceled', event, {cancelable: t rue});
7802 if (cancelEvent.defaultPrevented) { 8114 if (cancelEvent.defaultPrevented) {
7803 return; 8115 return;
7804 } 8116 }
7805 8117
7806 this._setCanceled(true); 8118 this._setCanceled(true);
7807 this.opened = false; 8119 this.opened = false;
7808 }, 8120 },
7809 8121
7810 _ensureSetup: function() { 8122 _ensureSetup: function() {
7811 if (this._overlaySetup) { 8123 if (this._overlaySetup) {
7812 return; 8124 return;
7813 } 8125 }
7814 this._overlaySetup = true; 8126 this._overlaySetup = true;
7815 this.style.outline = 'none'; 8127 this.style.outline = 'none';
7816 this.style.display = 'none'; 8128 this.style.display = 'none';
7817 }, 8129 },
7818 8130
7819 _openedChanged: function() { 8131 _openedChanged: function() {
7820 if (this.opened) { 8132 if (this.opened) {
7821 this.removeAttribute('aria-hidden'); 8133 this.removeAttribute('aria-hidden');
7822 } else { 8134 } else {
7823 this.setAttribute('aria-hidden', 'true'); 8135 this.setAttribute('aria-hidden', 'true');
7824 Polymer.dom(this).unobserveNodes(this._observer);
7825 } 8136 }
7826 8137
7827 // wait to call after ready only if we're initially open 8138 // wait to call after ready only if we're initially open
7828 if (!this._overlaySetup) { 8139 if (!this._overlaySetup) {
7829 this._callOpenedWhenReady = this.opened;
7830 return; 8140 return;
7831 } 8141 }
7832 8142
7833 this._manager.trackBackdrop(this); 8143 this._manager.trackBackdrop(this);
7834 8144
7835 if (this.opened) { 8145 if (this.opened) {
7836 this._prepareRenderOpened(); 8146 this._prepareRenderOpened();
7837 } 8147 }
7838 8148
7839 if (this._openChangedAsync) { 8149 if (this._openChangedAsync) {
(...skipping 54 matching lines...) Expand 10 before | Expand all | Expand 10 after
7894 node.addEventListener(event, boundListener, capture); 8204 node.addEventListener(event, boundListener, capture);
7895 } else { 8205 } else {
7896 // disable document-wide tap recognizer 8206 // disable document-wide tap recognizer
7897 if (event === 'tap') { 8207 if (event === 'tap') {
7898 Polymer.Gestures.remove(document, 'tap', null); 8208 Polymer.Gestures.remove(document, 'tap', null);
7899 } 8209 }
7900 node.removeEventListener(event, boundListener, capture); 8210 node.removeEventListener(event, boundListener, capture);
7901 } 8211 }
7902 }, 8212 },
7903 8213
7904 _toggleListeners: function () { 8214 _toggleListeners: function() {
7905 this._toggleListener(this.opened, document, 'tap', this._boundOnCaptureCli ck, true); 8215 this._toggleListener(this.opened, document, 'tap', this._boundOnCaptureCli ck, true);
7906 this._toggleListener(this.opened, document, 'keydown', this._boundOnCaptur eKeydown, true);
7907 this._toggleListener(this.opened, document, 'focus', this._boundOnCaptureF ocus, true); 8216 this._toggleListener(this.opened, document, 'focus', this._boundOnCaptureF ocus, true);
7908 }, 8217 },
7909 8218
7910 // tasks which must occur before opening; e.g. making the element visible 8219 // tasks which must occur before opening; e.g. making the element visible
7911 _prepareRenderOpened: function() { 8220 _prepareRenderOpened: function() {
8221
7912 this._manager.addOverlay(this); 8222 this._manager.addOverlay(this);
7913 8223
8224 // Needed to calculate the size of the overlay so that transitions on its size
8225 // will have the correct starting points.
7914 this._preparePositioning(); 8226 this._preparePositioning();
7915 this.fit(); 8227 this.fit();
7916 this._finishPositioning(); 8228 this._finishPositioning();
7917 8229
7918 if (this.withBackdrop) { 8230 if (this.withBackdrop) {
7919 this.backdropElement.prepare(); 8231 this.backdropElement.prepare();
7920 } 8232 }
8233
8234 // Safari will apply the focus to the autofocus element when displayed for the first time,
8235 // so we blur it. Later, _applyFocus will set the focus if necessary.
8236 if (this.noAutoFocus && document.activeElement === this._focusNode) {
8237 this._focusNode.blur();
8238 }
7921 }, 8239 },
7922 8240
7923 // tasks which cause the overlay to actually open; typically play an 8241 // tasks which cause the overlay to actually open; typically play an
7924 // animation 8242 // animation
7925 _renderOpened: function() { 8243 _renderOpened: function() {
7926 if (this.withBackdrop) { 8244 if (this.withBackdrop) {
7927 this.backdropElement.open(); 8245 this.backdropElement.open();
7928 } 8246 }
7929 this._finishRenderOpened(); 8247 this._finishRenderOpened();
7930 }, 8248 },
7931 8249
7932 _renderClosed: function() { 8250 _renderClosed: function() {
7933 if (this.withBackdrop) { 8251 if (this.withBackdrop) {
7934 this.backdropElement.close(); 8252 this.backdropElement.close();
7935 } 8253 }
7936 this._finishRenderClosed(); 8254 this._finishRenderClosed();
7937 }, 8255 },
7938 8256
7939 _finishRenderOpened: function() { 8257 _finishRenderOpened: function() {
7940 // focus the child node with [autofocus] 8258 // This ensures the overlay is visible before we set the focus
8259 // (by calling _onIronResize -> refit).
8260 this.notifyResize();
8261 // Focus the child node with [autofocus]
7941 this._applyFocus(); 8262 this._applyFocus();
7942 8263
7943 this._observer = Polymer.dom(this).observeNodes(this.notifyResize);
7944 this.fire('iron-overlay-opened'); 8264 this.fire('iron-overlay-opened');
7945 }, 8265 },
7946 8266
7947 _finishRenderClosed: function() { 8267 _finishRenderClosed: function() {
7948 // hide the overlay and remove the backdrop 8268 // Hide the overlay and remove the backdrop.
7949 this.resetFit(); 8269 this.resetFit();
7950 this.style.display = 'none'; 8270 this.style.display = 'none';
7951 this._manager.removeOverlay(this); 8271 this._manager.removeOverlay(this);
7952 8272
7953 this._focusedChild = null;
7954 this._applyFocus(); 8273 this._applyFocus();
8274 this.notifyResize();
7955 8275
7956 this.notifyResize();
7957 this.fire('iron-overlay-closed', this.closingReason); 8276 this.fire('iron-overlay-closed', this.closingReason);
7958 }, 8277 },
7959 8278
7960 _preparePositioning: function() { 8279 _preparePositioning: function() {
7961 this.style.transition = this.style.webkitTransition = 'none'; 8280 this.style.transition = this.style.webkitTransition = 'none';
7962 this.style.transform = this.style.webkitTransform = 'none'; 8281 this.style.transform = this.style.webkitTransform = 'none';
7963 this.style.display = ''; 8282 this.style.display = '';
7964 }, 8283 },
7965 8284
7966 _finishPositioning: function() { 8285 _finishPositioning: function() {
7967 this.style.display = 'none'; 8286 this.style.display = 'none';
7968 this.style.transform = this.style.webkitTransform = ''; 8287 this.style.transform = this.style.webkitTransform = '';
7969 // force layout to avoid application of transform 8288 // Force layout layout to avoid application of transform.
7970 /** @suppress {suspiciousCode} */ this.offsetWidth; 8289 // Set offsetWidth to itself so that compilers won't remove it.
8290 this.offsetWidth = this.offsetWidth;
7971 this.style.transition = this.style.webkitTransition = ''; 8291 this.style.transition = this.style.webkitTransition = '';
7972 }, 8292 },
7973 8293
7974 _applyFocus: function() { 8294 _applyFocus: function() {
7975 if (this.opened) { 8295 if (this.opened) {
7976 if (!this.noAutoFocus) { 8296 if (!this.noAutoFocus) {
7977 this._focusNode.focus(); 8297 this._focusNode.focus();
7978 } 8298 }
7979 } else { 8299 } else {
7980 this._focusNode.blur(); 8300 this._focusNode.blur();
8301 this._focusedChild = null;
7981 this._manager.focusOverlay(); 8302 this._manager.focusOverlay();
7982 } 8303 }
7983 }, 8304 },
7984 8305
7985 _onCaptureClick: function(event) { 8306 _onCaptureClick: function(event) {
7986 if (this._manager.currentOverlay() === this && 8307 if (this._manager.currentOverlay() === this &&
7987 Polymer.dom(event).path.indexOf(this) === -1) { 8308 Polymer.dom(event).path.indexOf(this) === -1) {
7988 if (this.noCancelOnOutsideClick) { 8309 if (this.noCancelOnOutsideClick) {
7989 this._applyFocus(); 8310 this._applyFocus();
7990 } else { 8311 } else {
7991 this.cancel(); 8312 this.cancel(event);
7992 } 8313 }
7993 } 8314 }
7994 }, 8315 },
7995 8316
7996 _onCaptureKeydown: function(event) {
7997 var ESC = 27;
7998 if (this._manager.currentOverlay() === this &&
7999 !this.noCancelOnEscKey &&
8000 event.keyCode === ESC) {
8001 this.cancel();
8002 }
8003 },
8004
8005 _onCaptureFocus: function (event) { 8317 _onCaptureFocus: function (event) {
8006 if (this._manager.currentOverlay() === this && 8318 if (this._manager.currentOverlay() === this && this.withBackdrop) {
8007 this.withBackdrop) {
8008 var path = Polymer.dom(event).path; 8319 var path = Polymer.dom(event).path;
8009 if (path.indexOf(this) === -1) { 8320 if (path.indexOf(this) === -1) {
8010 event.stopPropagation(); 8321 event.stopPropagation();
8011 this._applyFocus(); 8322 this._applyFocus();
8012 } else { 8323 } else {
8013 this._focusedChild = path[0]; 8324 this._focusedChild = path[0];
8014 } 8325 }
8015 } 8326 }
8016 }, 8327 },
8017 8328
8018 _onIronResize: function() { 8329 _onIronResize: function() {
8019 if (this.opened) { 8330 if (this.opened) {
8020 this.refit(); 8331 this.refit();
8021 } 8332 }
8333 },
8334
8335 /**
8336 * @protected
8337 * Will call notifyResize if overlay is opened.
8338 * Can be overridden in order to avoid multiple observers on the same node.
8339 */
8340 _onNodesChange: function() {
8341 if (this.opened) {
8342 this.notifyResize();
8343 }
8344 // Store it so we don't query too much.
8345 var focusableNodes = this._focusableNodes;
8346 this.__firstFocusableNode = focusableNodes[0];
8347 this.__lastFocusableNode = focusableNodes[focusableNodes.length - 1];
8348 },
8349
8350 __onEsc: function(event) {
8351 // Not opened or not on top, so return.
8352 if (this._manager.currentOverlay() !== this) {
8353 return;
8354 }
8355 if (!this.noCancelOnEscKey) {
8356 this.cancel(event);
8357 }
8358 },
8359
8360 __onTab: function(event) {
8361 // Not opened or not on top, so return.
8362 if (this._manager.currentOverlay() !== this) {
8363 return;
8364 }
8365 // TAB wraps from last to first focusable.
8366 // Shift + TAB wraps from first to last focusable.
8367 var shift = event.detail.keyboardEvent.shiftKey;
8368 var nodeToCheck = shift ? this.__firstFocusableNode : this.__lastFocusable Node;
8369 var nodeToSet = shift ? this.__lastFocusableNode : this.__firstFocusableNo de;
8370 if (this.withBackdrop && this._focusedChild === nodeToCheck) {
8371 // We set here the _focusedChild so that _onCaptureFocus will handle the
8372 // wrapping of the focus (the next event after tab is focus).
8373 this._focusedChild = nodeToSet;
8374 }
8022 } 8375 }
8023
8024 /**
8025 * Fired after the `iron-overlay` opens.
8026 * @event iron-overlay-opened
8027 */
8028
8029 /**
8030 * Fired when the `iron-overlay` is canceled, but before it is closed.
8031 * Cancel the event to prevent the `iron-overlay` from closing.
8032 * @event iron-overlay-canceled
8033 */
8034
8035 /**
8036 * Fired after the `iron-overlay` closes.
8037 * @event iron-overlay-closed
8038 * @param {{canceled: (boolean|undefined)}} set to the `closingReason` attribute
8039 */
8040 }; 8376 };
8041 8377
8042 /** @polymerBehavior */ 8378 /** @polymerBehavior */
8043 Polymer.IronOverlayBehavior = [Polymer.IronFitBehavior, Polymer.IronResizableB ehavior, Polymer.IronOverlayBehaviorImpl]; 8379 Polymer.IronOverlayBehavior = [Polymer.IronA11yKeysBehavior, Polymer.IronFitBe havior, Polymer.IronResizableBehavior, Polymer.IronOverlayBehaviorImpl];
8380
8381 /**
8382 * Fired after the `iron-overlay` opens.
8383 * @event iron-overlay-opened
8384 */
8385
8386 /**
8387 * Fired when the `iron-overlay` is canceled, but before it is closed.
8388 * Cancel the event to prevent the `iron-overlay` from closing.
8389 * @event iron-overlay-canceled
8390 * @param {Event} event The closing of the `iron-overlay` can be prevented
8391 * by calling `event.preventDefault()`. The `event.detail` is the original even t that originated
8392 * the canceling (e.g. ESC keyboard event or click event outside the `iron-over lay`).
8393 */
8394
8395 /**
8396 * Fired after the `iron-overlay` closes.
8397 * @event iron-overlay-closed
8398 * @param {{canceled: (boolean|undefined)}} closingReason Contains `canceled` ( whether the overlay was canceled).
8399 */
8044 /** 8400 /**
8045 * Use `Polymer.NeonAnimationBehavior` to implement an animation. 8401 * Use `Polymer.NeonAnimationBehavior` to implement an animation.
8046 * @polymerBehavior 8402 * @polymerBehavior
8047 */ 8403 */
8048 Polymer.NeonAnimationBehavior = { 8404 Polymer.NeonAnimationBehavior = {
8049 8405
8050 properties: { 8406 properties: {
8051 8407
8052 /** 8408 /**
8053 * Defines the animation timing. 8409 * Defines the animation timing.
(...skipping 2411 matching lines...) Expand 10 before | Expand all | Expand 10 after
10465 Manager.get().updateItem_(index, data); 10821 Manager.get().updateItem_(index, data);
10466 }; 10822 };
10467 10823
10468 return {Manager: Manager}; 10824 return {Manager: Manager};
10469 }); 10825 });
10470 // Copyright 2015 The Chromium Authors. All rights reserved. 10826 // Copyright 2015 The Chromium Authors. All rights reserved.
10471 // Use of this source code is governed by a BSD-style license that can be 10827 // Use of this source code is governed by a BSD-style license that can be
10472 // found in the LICENSE file. 10828 // found in the LICENSE file.
10473 10829
10474 window.addEventListener('load', downloads.Manager.onLoad); 10830 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