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

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

Issue 1845223007: MD Downloads: revulcanize in a polymer.html world (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@vulcanize-bug
Patch Set: Created 4 years, 8 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. 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 2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file. 3 // found in the LICENSE file.
4 4
5 /** 5 /**
6 * @fileoverview PromiseResolver is a helper class that allows creating a 6 * @fileoverview PromiseResolver is a helper class that allows creating a
7 * Promise that will be fulfilled (resolved or rejected) some time later. 7 * Promise that will be fulfilled (resolved or rejected) some time later.
8 * 8 *
9 * Example: 9 * Example:
10 * var resolver = new PromiseResolver(); 10 * var resolver = new PromiseResolver();
(...skipping 1413 matching lines...) Expand 10 before | Expand all | Expand 10 after
1424 var elm = document.createElement(type); 1424 var elm = document.createElement(type);
1425 elm.className = className; 1425 elm.className = className;
1426 return elm; 1426 return elm;
1427 } 1427 }
1428 1428
1429 /** 1429 /**
1430 * webkitTransitionEnd does not always fire (e.g. when animation is aborted 1430 * webkitTransitionEnd does not always fire (e.g. when animation is aborted
1431 * or when no paint happens during the animation). This function sets up 1431 * or when no paint happens during the animation). This function sets up
1432 * a timer and emulate the event if it is not fired when the timer expires. 1432 * a timer and emulate the event if it is not fired when the timer expires.
1433 * @param {!HTMLElement} el The element to watch for webkitTransitionEnd. 1433 * @param {!HTMLElement} el The element to watch for webkitTransitionEnd.
1434 * @param {number} timeOut The maximum wait time in milliseconds for the 1434 * @param {number=} opt_timeOut The maximum wait time in milliseconds for the
1435 * webkitTransitionEnd to happen. 1435 * webkitTransitionEnd to happen. If not specified, it is fetched from |el|
1436 * using the transitionDuration style value.
1436 */ 1437 */
1437 function ensureTransitionEndEvent(el, timeOut) { 1438 function ensureTransitionEndEvent(el, opt_timeOut) {
1439 if (opt_timeOut === undefined) {
1440 var style = getComputedStyle(el);
1441 opt_timeOut = parseFloat(style.transitionDuration) * 1000;
1442
1443 // Give an additional 50ms buffer for the animation to complete.
1444 opt_timeOut += 50;
1445 }
1446
1438 var fired = false; 1447 var fired = false;
1439 el.addEventListener('webkitTransitionEnd', function f(e) { 1448 el.addEventListener('webkitTransitionEnd', function f(e) {
1440 el.removeEventListener('webkitTransitionEnd', f); 1449 el.removeEventListener('webkitTransitionEnd', f);
1441 fired = true; 1450 fired = true;
1442 }); 1451 });
1443 window.setTimeout(function() { 1452 window.setTimeout(function() {
1444 if (!fired) 1453 if (!fired)
1445 cr.dispatchSimpleEvent(el, 'webkitTransitionEnd', true); 1454 cr.dispatchSimpleEvent(el, 'webkitTransitionEnd', true);
1446 }, timeOut); 1455 }, opt_timeOut);
1447 } 1456 }
1448 1457
1449 /** 1458 /**
1450 * Alias for document.scrollTop getter. 1459 * Alias for document.scrollTop getter.
1451 * @param {!HTMLDocument} doc The document node where information will be 1460 * @param {!HTMLDocument} doc The document node where information will be
1452 * queried from. 1461 * queried from.
1453 * @return {number} The Y document scroll offset. 1462 * @return {number} The Y document scroll offset.
1454 */ 1463 */
1455 function scrollTopForDocument(doc) { 1464 function scrollTopForDocument(doc) {
1456 return doc.documentElement.scrollTop || doc.body.scrollTop; 1465 return doc.documentElement.scrollTop || doc.body.scrollTop;
(...skipping 58 matching lines...) Expand 10 before | Expand all | Expand 10 after
1515 } 1524 }
1516 1525
1517 /** 1526 /**
1518 * Quote a string so it can be used in a regular expression. 1527 * Quote a string so it can be used in a regular expression.
1519 * @param {string} str The source string. 1528 * @param {string} str The source string.
1520 * @return {string} The escaped string. 1529 * @return {string} The escaped string.
1521 */ 1530 */
1522 function quoteString(str) { 1531 function quoteString(str) {
1523 return str.replace(/([\\\.\+\*\?\[\^\]\$\(\)\{\}\=\!\<\>\|\:])/g, '\\$1'); 1532 return str.replace(/([\\\.\+\*\?\[\^\]\$\(\)\{\}\=\!\<\>\|\:])/g, '\\$1');
1524 }; 1533 };
1525 // Copyright (c) 2013 The Chromium Authors. All rights reserved.
1526 // Use of this source code is governed by a BSD-style license that can be
1527 // found in the LICENSE file.
1528
1529 /**
1530 * @fileoverview Assertion support.
1531 */
1532
1533 /**
1534 * Verify |condition| is truthy and return |condition| if so.
1535 * @template T
1536 * @param {T} condition A condition to check for truthiness. Note that this
1537 * may be used to test whether a value is defined or not, and we don't want
1538 * to force a cast to Boolean.
1539 * @param {string=} opt_message A message to show on failure.
1540 * @return {T} A non-null |condition|.
1541 */
1542 function assert(condition, opt_message) {
1543 if (!condition) {
1544 var message = 'Assertion failed';
1545 if (opt_message)
1546 message = message + ': ' + opt_message;
1547 var error = new Error(message);
1548 var global = function() { return this; }();
1549 if (global.traceAssertionsForTesting)
1550 console.warn(error.stack);
1551 throw error;
1552 }
1553 return condition;
1554 }
1555
1556 /**
1557 * Call this from places in the code that should never be reached.
1558 *
1559 * For example, handling all the values of enum with a switch() like this:
1560 *
1561 * function getValueFromEnum(enum) {
1562 * switch (enum) {
1563 * case ENUM_FIRST_OF_TWO:
1564 * return first
1565 * case ENUM_LAST_OF_TWO:
1566 * return last;
1567 * }
1568 * assertNotReached();
1569 * return document;
1570 * }
1571 *
1572 * This code should only be hit in the case of serious programmer error or
1573 * unexpected input.
1574 *
1575 * @param {string=} opt_message A message to show when this is hit.
1576 */
1577 function assertNotReached(opt_message) {
1578 assert(false, opt_message || 'Unreachable code hit');
1579 }
1580
1581 /**
1582 * @param {*} value The value to check.
1583 * @param {function(new: T, ...)} type A user-defined constructor.
1584 * @param {string=} opt_message A message to show when this is hit.
1585 * @return {T}
1586 * @template T
1587 */
1588 function assertInstanceof(value, type, opt_message) {
1589 // We don't use assert immediately here so that we avoid constructing an error
1590 // message if we don't have to.
1591 if (!(value instanceof type)) {
1592 assertNotReached(opt_message || 'Value ' + value +
1593 ' is not a[n] ' + (type.name || typeof type));
1594 }
1595 return value;
1596 };
1597 // Copyright 2015 The Chromium Authors. All rights reserved.
1598 // Use of this source code is governed by a BSD-style license that can be
1599 // found in the LICENSE file.
1600
1601 cr.define('downloads', function() {
1602 /**
1603 * @param {string} chromeSendName
1604 * @return {function(string):void} A chrome.send() callback with curried name.
1605 */
1606 function chromeSendWithId(chromeSendName) {
1607 return function(id) { chrome.send(chromeSendName, [id]); };
1608 }
1609
1610 /** @constructor */
1611 function ActionService() {
1612 /** @private {Array<string>} */
1613 this.searchTerms_ = [];
1614 }
1615
1616 /**
1617 * @param {string} s
1618 * @return {string} |s| without whitespace at the beginning or end.
1619 */
1620 function trim(s) { return s.trim(); }
1621
1622 /**
1623 * @param {string|undefined} value
1624 * @return {boolean} Whether |value| is truthy.
1625 */
1626 function truthy(value) { return !!value; }
1627
1628 /**
1629 * @param {string} searchText Input typed by the user into a search box.
1630 * @return {Array<string>} A list of terms extracted from |searchText|.
1631 */
1632 ActionService.splitTerms = function(searchText) {
1633 // Split quoted terms (e.g., 'The "lazy" dog' => ['The', 'lazy', 'dog']).
1634 return searchText.split(/"([^"]*)"/).map(trim).filter(truthy);
1635 };
1636
1637 ActionService.prototype = {
1638 /** @param {string} id ID of the download to cancel. */
1639 cancel: chromeSendWithId('cancel'),
1640
1641 /** Instructs the browser to clear all finished downloads. */
1642 clearAll: function() {
1643 if (loadTimeData.getBoolean('allowDeletingHistory')) {
1644 chrome.send('clearAll');
1645 this.search('');
1646 }
1647 },
1648
1649 /** @param {string} id ID of the dangerous download to discard. */
1650 discardDangerous: chromeSendWithId('discardDangerous'),
1651
1652 /** @param {string} url URL of a file to download. */
1653 download: function(url) {
1654 var a = document.createElement('a');
1655 a.href = url;
1656 a.setAttribute('download', '');
1657 a.click();
1658 },
1659
1660 /** @param {string} id ID of the download that the user started dragging. */
1661 drag: chromeSendWithId('drag'),
1662
1663 /** Loads more downloads with the current search terms. */
1664 loadMore: function() {
1665 chrome.send('getDownloads', this.searchTerms_);
1666 },
1667
1668 /**
1669 * @return {boolean} Whether the user is currently searching for downloads
1670 * (i.e. has a non-empty search term).
1671 */
1672 isSearching: function() {
1673 return this.searchTerms_.length > 0;
1674 },
1675
1676 /** Opens the current local destination for downloads. */
1677 openDownloadsFolder: chrome.send.bind(chrome, 'openDownloadsFolder'),
1678
1679 /**
1680 * @param {string} id ID of the download to run locally on the user's box.
1681 */
1682 openFile: chromeSendWithId('openFile'),
1683
1684 /** @param {string} id ID the of the progressing download to pause. */
1685 pause: chromeSendWithId('pause'),
1686
1687 /** @param {string} id ID of the finished download to remove. */
1688 remove: chromeSendWithId('remove'),
1689
1690 /** @param {string} id ID of the paused download to resume. */
1691 resume: chromeSendWithId('resume'),
1692
1693 /**
1694 * @param {string} id ID of the dangerous download to save despite
1695 * warnings.
1696 */
1697 saveDangerous: chromeSendWithId('saveDangerous'),
1698
1699 /** @param {string} searchText What to search for. */
1700 search: function(searchText) {
1701 var searchTerms = ActionService.splitTerms(searchText);
1702 var sameTerms = searchTerms.length == this.searchTerms_.length;
1703
1704 for (var i = 0; sameTerms && i < searchTerms.length; ++i) {
1705 if (searchTerms[i] != this.searchTerms_[i])
1706 sameTerms = false;
1707 }
1708
1709 if (sameTerms)
1710 return;
1711
1712 this.searchTerms_ = searchTerms;
1713 this.loadMore();
1714 },
1715
1716 /**
1717 * Shows the local folder a finished download resides in.
1718 * @param {string} id ID of the download to show.
1719 */
1720 show: chromeSendWithId('show'),
1721
1722 /** Undo download removal. */
1723 undo: chrome.send.bind(chrome, 'undo'),
1724 };
1725
1726 cr.addSingletonGetter(ActionService);
1727
1728 return {ActionService: ActionService};
1729 });
1730 // Copyright 2015 The Chromium Authors. All rights reserved.
1731 // Use of this source code is governed by a BSD-style license that can be
1732 // found in the LICENSE file.
1733
1734 cr.define('downloads', function() {
1735 /**
1736 * Explains why a download is in DANGEROUS state.
1737 * @enum {string}
1738 */
1739 var DangerType = {
1740 NOT_DANGEROUS: 'NOT_DANGEROUS',
1741 DANGEROUS_FILE: 'DANGEROUS_FILE',
1742 DANGEROUS_URL: 'DANGEROUS_URL',
1743 DANGEROUS_CONTENT: 'DANGEROUS_CONTENT',
1744 UNCOMMON_CONTENT: 'UNCOMMON_CONTENT',
1745 DANGEROUS_HOST: 'DANGEROUS_HOST',
1746 POTENTIALLY_UNWANTED: 'POTENTIALLY_UNWANTED',
1747 };
1748
1749 /**
1750 * The states a download can be in. These correspond to states defined in
1751 * DownloadsDOMHandler::CreateDownloadItemValue
1752 * @enum {string}
1753 */
1754 var States = {
1755 IN_PROGRESS: 'IN_PROGRESS',
1756 CANCELLED: 'CANCELLED',
1757 COMPLETE: 'COMPLETE',
1758 PAUSED: 'PAUSED',
1759 DANGEROUS: 'DANGEROUS',
1760 INTERRUPTED: 'INTERRUPTED',
1761 };
1762
1763 return {
1764 DangerType: DangerType,
1765 States: States,
1766 };
1767 });
1768 // Copyright 2014 The Chromium Authors. All rights reserved.
1769 // Use of this source code is governed by a BSD-style license that can be
1770 // found in the LICENSE file.
1771
1772 // Action links are elements that are used to perform an in-page navigation or
1773 // action (e.g. showing a dialog).
1774 //
1775 // They look like normal anchor (<a>) tags as their text color is blue. However,
1776 // they're subtly different as they're not initially underlined (giving users a
1777 // clue that underlined links navigate while action links don't).
1778 //
1779 // Action links look very similar to normal links when hovered (hand cursor,
1780 // underlined). This gives the user an idea that clicking this link will do
1781 // something similar to navigation but in the same page.
1782 //
1783 // They can be created in JavaScript like this:
1784 //
1785 // var link = document.createElement('a', 'action-link'); // Note second arg.
1786 //
1787 // or with a constructor like this:
1788 //
1789 // var link = new ActionLink();
1790 //
1791 // They can be used easily from HTML as well, like so:
1792 //
1793 // <a is="action-link">Click me!</a>
1794 //
1795 // NOTE: <action-link> and document.createElement('action-link') don't work.
1796
1797 /**
1798 * @constructor
1799 * @extends {HTMLAnchorElement}
1800 */
1801 var ActionLink = document.registerElement('action-link', {
1802 prototype: {
1803 __proto__: HTMLAnchorElement.prototype,
1804
1805 /** @this {ActionLink} */
1806 createdCallback: function() {
1807 // Action links can start disabled (e.g. <a is="action-link" disabled>).
1808 this.tabIndex = this.disabled ? -1 : 0;
1809
1810 if (!this.hasAttribute('role'))
1811 this.setAttribute('role', 'link');
1812
1813 this.addEventListener('keydown', function(e) {
1814 if (!this.disabled && e.keyIdentifier == 'Enter' && !this.href) {
1815 // Schedule a click asynchronously because other 'keydown' handlers
1816 // may still run later (e.g. document.addEventListener('keydown')).
1817 // Specifically options dialogs break when this timeout isn't here.
1818 // NOTE: this affects the "trusted" state of the ensuing click. I
1819 // haven't found anything that breaks because of this (yet).
1820 window.setTimeout(this.click.bind(this), 0);
1821 }
1822 });
1823
1824 function preventDefault(e) {
1825 e.preventDefault();
1826 }
1827
1828 function removePreventDefault() {
1829 document.removeEventListener('selectstart', preventDefault);
1830 document.removeEventListener('mouseup', removePreventDefault);
1831 }
1832
1833 this.addEventListener('mousedown', function() {
1834 // This handlers strives to match the behavior of <a href="...">.
1835
1836 // While the mouse is down, prevent text selection from dragging.
1837 document.addEventListener('selectstart', preventDefault);
1838 document.addEventListener('mouseup', removePreventDefault);
1839
1840 // If focus started via mouse press, don't show an outline.
1841 if (document.activeElement != this)
1842 this.classList.add('no-outline');
1843 });
1844
1845 this.addEventListener('blur', function() {
1846 this.classList.remove('no-outline');
1847 });
1848 },
1849
1850 /** @type {boolean} */
1851 set disabled(disabled) {
1852 if (disabled)
1853 HTMLAnchorElement.prototype.setAttribute.call(this, 'disabled', '');
1854 else
1855 HTMLAnchorElement.prototype.removeAttribute.call(this, 'disabled');
1856 this.tabIndex = disabled ? -1 : 0;
1857 },
1858 get disabled() {
1859 return this.hasAttribute('disabled');
1860 },
1861
1862 /** @override */
1863 setAttribute: function(attr, val) {
1864 if (attr.toLowerCase() == 'disabled')
1865 this.disabled = true;
1866 else
1867 HTMLAnchorElement.prototype.setAttribute.apply(this, arguments);
1868 },
1869
1870 /** @override */
1871 removeAttribute: function(attr) {
1872 if (attr.toLowerCase() == 'disabled')
1873 this.disabled = false;
1874 else
1875 HTMLAnchorElement.prototype.removeAttribute.apply(this, arguments);
1876 },
1877 },
1878
1879 extends: 'a',
1880 });
1881 // Copyright (c) 2012 The Chromium Authors. All rights reserved.
1882 // Use of this source code is governed by a BSD-style license that can be
1883 // found in the LICENSE file.
1884
1885 // <include src="../../../../ui/webui/resources/js/i18n_template_no_process.js">
1886
1887 i18nTemplate.process(document, loadTimeData);
1888 /** 1534 /**
1889 * `IronResizableBehavior` is a behavior that can be used in Polymer elements to 1535 * `IronResizableBehavior` is a behavior that can be used in Polymer elements to
1890 * coordinate the flow of resize events between "resizers" (elements that cont rol the 1536 * coordinate the flow of resize events between "resizers" (elements that cont rol the
1891 * size or hidden state of their children) and "resizables" (elements that nee d to be 1537 * size or hidden state of their children) and "resizables" (elements that nee d to be
1892 * notified when they are resized or un-hidden by their parents in order to ta ke 1538 * notified when they are resized or un-hidden by their parents in order to ta ke
1893 * action on their new measurements). 1539 * action on their new measurements).
1894 *
1895 * Elements that perform measurement should add the `IronResizableBehavior` be havior to 1540 * Elements that perform measurement should add the `IronResizableBehavior` be havior to
1896 * their element definition and listen for the `iron-resize` event on themselv es. 1541 * their element definition and listen for the `iron-resize` event on themselv es.
1897 * This event will be fired when they become showing after having been hidden, 1542 * This event will be fired when they become showing after having been hidden,
1898 * when they are resized explicitly by another resizable, or when the window h as been 1543 * when they are resized explicitly by another resizable, or when the window h as been
1899 * resized. 1544 * resized.
1900 *
1901 * Note, the `iron-resize` event is non-bubbling. 1545 * Note, the `iron-resize` event is non-bubbling.
1902 * 1546 *
1903 * @polymerBehavior Polymer.IronResizableBehavior 1547 * @polymerBehavior Polymer.IronResizableBehavior
1904 * @demo demo/index.html 1548 * @demo demo/index.html
1905 **/ 1549 **/
1906 Polymer.IronResizableBehavior = { 1550 Polymer.IronResizableBehavior = {
1907 properties: { 1551 properties: {
1908 /** 1552 /**
1909 * The closest ancestor element that implements `IronResizableBehavior`. 1553 * The closest ancestor element that implements `IronResizableBehavior`.
1910 */ 1554 */
(...skipping 643 matching lines...) Expand 10 before | Expand all | Expand 10 after
2554 window.removeEventListener('scroll', this._boundScrollHandler); 2198 window.removeEventListener('scroll', this._boundScrollHandler);
2555 } else if (this._oldScrollTarget.removeEventListener) { 2199 } else if (this._oldScrollTarget.removeEventListener) {
2556 this._oldScrollTarget.removeEventListener('scroll', this._boundScrollH andler); 2200 this._oldScrollTarget.removeEventListener('scroll', this._boundScrollH andler);
2557 } 2201 }
2558 this._oldScrollTarget = null; 2202 this._oldScrollTarget = null;
2559 } 2203 }
2560 if (isAttached) { 2204 if (isAttached) {
2561 // Support element id references 2205 // Support element id references
2562 if (typeof scrollTarget === 'string') { 2206 if (typeof scrollTarget === 'string') {
2563 2207
2564 var host = this.domHost; 2208 var ownerRoot = Polymer.dom(this).getOwnerRoot();
2565 this.scrollTarget = host && host.$ ? host.$[scrollTarget] : 2209 this.scrollTarget = (ownerRoot && ownerRoot.$) ?
2566 Polymer.dom(this.ownerDocument).querySelector('#' + scrollTarget); 2210 ownerRoot.$[scrollTarget] : Polymer.dom(this.ownerDocument).queryS elector('#' + scrollTarget);
2567 2211
2568 } else if (this._scrollHandler) { 2212 } else if (this._scrollHandler) {
2569 2213
2570 this._boundScrollHandler = this._boundScrollHandler || this._scrollHan dler.bind(this); 2214 this._boundScrollHandler = this._boundScrollHandler || this._scrollHan dler.bind(this);
2571 // Add a new listener 2215 // Add a new listener
2572 if (scrollTarget === this._doc) { 2216 if (scrollTarget === this._doc) {
2573 window.addEventListener('scroll', this._boundScrollHandler); 2217 window.addEventListener('scroll', this._boundScrollHandler);
2574 if (this._scrollTop !== 0 || this._scrollLeft !== 0) { 2218 if (this._scrollTop !== 0 || this._scrollLeft !== 0) {
2575 this._scrollHandler(); 2219 this._scrollHandler();
2576 } 2220 }
(...skipping 78 matching lines...) Expand 10 before | Expand all | Expand 10 after
2655 window.scrollTo(left, window.pageYOffset); 2299 window.scrollTo(left, window.pageYOffset);
2656 } else if (this._isValidScrollTarget()) { 2300 } else if (this._isValidScrollTarget()) {
2657 this.scrollTarget.scrollLeft = left; 2301 this.scrollTarget.scrollLeft = left;
2658 } 2302 }
2659 }, 2303 },
2660 2304
2661 /** 2305 /**
2662 * Scrolls the content to a particular place. 2306 * Scrolls the content to a particular place.
2663 * 2307 *
2664 * @method scroll 2308 * @method scroll
2309 * @param {number} top The top position
2665 * @param {number} left The left position 2310 * @param {number} left The left position
2666 * @param {number} top The top position
2667 */ 2311 */
2668 scroll: function(left, top) { 2312 scroll: function(top, left) {
2669 if (this.scrollTarget === this._doc) { 2313 if (this.scrollTarget === this._doc) {
2670 window.scrollTo(left, top); 2314 window.scrollTo(top, left);
2671 } else if (this._isValidScrollTarget()) { 2315 } else if (this._isValidScrollTarget()) {
2316 this.scrollTarget.scrollTop = top;
2672 this.scrollTarget.scrollLeft = left; 2317 this.scrollTarget.scrollLeft = left;
2673 this.scrollTarget.scrollTop = top;
2674 } 2318 }
2675 }, 2319 },
2676 2320
2677 /** 2321 /**
2678 * Gets the width of the scroll target. 2322 * Gets the width of the scroll target.
2679 * 2323 *
2680 * @type {number} 2324 * @type {number}
2681 */ 2325 */
2682 get _scrollTargetWidth() { 2326 get _scrollTargetWidth() {
2683 if (this._isValidScrollTarget()) { 2327 if (this._isValidScrollTarget()) {
(...skipping 136 matching lines...) Expand 10 before | Expand all | Expand 10 after
2820 'enter': '_didEnter' 2464 'enter': '_didEnter'
2821 }, 2465 },
2822 2466
2823 /** 2467 /**
2824 * The ratio of hidden tiles that should remain in the scroll direction. 2468 * The ratio of hidden tiles that should remain in the scroll direction.
2825 * Recommended value ~0.5, so it will distribute tiles evely in both directi ons. 2469 * Recommended value ~0.5, so it will distribute tiles evely in both directi ons.
2826 */ 2470 */
2827 _ratio: 0.5, 2471 _ratio: 0.5,
2828 2472
2829 /** 2473 /**
2830 * The padding-top value for the list. 2474 * The padding-top value of the `scroller` element
2831 */ 2475 */
2832 _scrollerPaddingTop: 0, 2476 _scrollerPaddingTop: 0,
2833 2477
2834 /** 2478 /**
2835 * This value is the same as `scrollTop`. 2479 * This value is the same as `scrollTop`.
2836 */ 2480 */
2837 _scrollPosition: 0, 2481 _scrollPosition: 0,
2838 2482
2839 /** 2483 /**
2484 * The number of tiles in the DOM.
2485 */
2486 _physicalCount: 0,
2487
2488 /**
2489 * The k-th tile that is at the top of the scrolling list.
2490 */
2491 _physicalStart: 0,
2492
2493 /**
2494 * The k-th tile that is at the bottom of the scrolling list.
2495 */
2496 _physicalEnd: 0,
2497
2498 /**
2840 * The sum of the heights of all the tiles in the DOM. 2499 * The sum of the heights of all the tiles in the DOM.
2841 */ 2500 */
2842 _physicalSize: 0, 2501 _physicalSize: 0,
2843 2502
2844 /** 2503 /**
2845 * The average `F` of the tiles observed till now. 2504 * The average `F` of the tiles observed till now.
2846 */ 2505 */
2847 _physicalAverage: 0, 2506 _physicalAverage: 0,
2848 2507
2849 /** 2508 /**
2850 * The number of tiles which `offsetHeight` > 0 observed until now. 2509 * The number of tiles which `offsetHeight` > 0 observed until now.
2851 */ 2510 */
2852 _physicalAverageCount: 0, 2511 _physicalAverageCount: 0,
2853 2512
2854 /** 2513 /**
2855 * The Y position of the item rendered in the `_physicalStart` 2514 * The Y position of the item rendered in the `_physicalStart`
2856 * tile relative to the scrolling list. 2515 * tile relative to the scrolling list.
2857 */ 2516 */
2858 _physicalTop: 0, 2517 _physicalTop: 0,
2859 2518
2860 /** 2519 /**
2861 * The number of items in the list. 2520 * The number of items in the list.
2862 */ 2521 */
2863 _virtualCount: 0, 2522 _virtualCount: 0,
2864 2523
2865 /** 2524 /**
2525 * The n-th item rendered in the `_physicalStart` tile.
2526 */
2527 _virtualStartVal: 0,
2528
2529 /**
2866 * A map between an item key and its physical item index 2530 * A map between an item key and its physical item index
2867 */ 2531 */
2868 _physicalIndexForKey: null, 2532 _physicalIndexForKey: null,
2869 2533
2870 /** 2534 /**
2871 * The estimated scroll height based on `_physicalAverage` 2535 * The estimated scroll height based on `_physicalAverage`
2872 */ 2536 */
2873 _estScrollHeight: 0, 2537 _estScrollHeight: 0,
2874 2538
2875 /** 2539 /**
(...skipping 25 matching lines...) Expand all
2901 */ 2565 */
2902 _firstVisibleIndexVal: null, 2566 _firstVisibleIndexVal: null,
2903 2567
2904 /** 2568 /**
2905 * A cached value for the last visible index. 2569 * A cached value for the last visible index.
2906 * See `lastVisibleIndex` 2570 * See `lastVisibleIndex`
2907 * @type {?number} 2571 * @type {?number}
2908 */ 2572 */
2909 _lastVisibleIndexVal: null, 2573 _lastVisibleIndexVal: null,
2910 2574
2575
2911 /** 2576 /**
2912 * A Polymer collection for the items. 2577 * A Polymer collection for the items.
2913 * @type {?Polymer.Collection} 2578 * @type {?Polymer.Collection}
2914 */ 2579 */
2915 _collection: null, 2580 _collection: null,
2916 2581
2917 /** 2582 /**
2918 * True if the current item list was rendered for the first time 2583 * True if the current item list was rendered for the first time
2919 * after attached. 2584 * after attached.
2920 */ 2585 */
2921 _itemsRendered: false, 2586 _itemsRendered: false,
2922 2587
2923 /** 2588 /**
2924 * The page that is currently rendered. 2589 * The page that is currently rendered.
2925 */ 2590 */
2926 _lastPage: null, 2591 _lastPage: null,
2927 2592
2928 /** 2593 /**
2929 * The max number of pages to render. One page is equivalent to the height o f the list. 2594 * The max number of pages to render. One page is equivalent to the height o f the list.
2930 */ 2595 */
2931 _maxPages: 3, 2596 _maxPages: 3,
2932 2597
2933 /** 2598 /**
2934 * The currently focused physical item. 2599 * The currently focused item index.
2935 */ 2600 */
2936 _focusedItem: null, 2601 _focusedIndex: 0,
2937
2938 /**
2939 * The index of the `_focusedItem`.
2940 */
2941 _focusedIndex: -1,
2942 2602
2943 /** 2603 /**
2944 * The the item that is focused if it is moved offscreen. 2604 * The the item that is focused if it is moved offscreen.
2945 * @private {?TemplatizerNode} 2605 * @private {?TemplatizerNode}
2946 */ 2606 */
2947 _offscreenFocusedItem: null, 2607 _offscreenFocusedItem: null,
2948 2608
2949 /** 2609 /**
2950 * The item that backfills the `_offscreenFocusedItem` in the physical items 2610 * The item that backfills the `_offscreenFocusedItem` in the physical items
2951 * list when that item is moved offscreen. 2611 * list when that item is moved offscreen.
(...skipping 15 matching lines...) Expand all
2967 }, 2627 },
2968 2628
2969 /** 2629 /**
2970 * The n-th item rendered in the last physical item. 2630 * The n-th item rendered in the last physical item.
2971 */ 2631 */
2972 get _virtualEnd() { 2632 get _virtualEnd() {
2973 return this._virtualStart + this._physicalCount - 1; 2633 return this._virtualStart + this._physicalCount - 1;
2974 }, 2634 },
2975 2635
2976 /** 2636 /**
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 /**
2991 * The lowest n-th value for an item such that it can be rendered in `_physi calStart`. 2637 * The lowest n-th value for an item such that it can be rendered in `_physi calStart`.
2992 */ 2638 */
2993 _minVirtualStart: 0, 2639 _minVirtualStart: 0,
2994 2640
2995 /** 2641 /**
2996 * The largest n-th value for an item such that it can be rendered in `_phys icalStart`. 2642 * The largest n-th value for an item such that it can be rendered in `_phys icalStart`.
2997 */ 2643 */
2998 get _maxVirtualStart() { 2644 get _maxVirtualStart() {
2999 return Math.max(0, this._virtualCount - this._physicalCount); 2645 return Math.max(0, this._virtualCount - this._physicalCount);
3000 }, 2646 },
3001 2647
3002 /** 2648 /**
3003 * The n-th item rendered in the `_physicalStart` tile. 2649 * The height of the physical content that isn't on the screen.
3004 */ 2650 */
3005 _virtualStartVal: 0, 2651 get _hiddenContentSize() {
3006 2652 return this._physicalSize - this._viewportSize;
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;
3013 }, 2653 },
3014 2654
3015 /** 2655 /**
3016 * The k-th tile that is at the top of the scrolling list. 2656 * The maximum scroll top value.
3017 */ 2657 */
3018 _physicalStartVal: 0, 2658 get _maxScrollTop() {
3019 2659 return this._estScrollHeight - this._viewportSize;
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;
3030 }, 2660 },
3031 2661
3032 /** 2662 /**
3033 * The number of tiles in the DOM. 2663 * Sets the n-th item rendered in `_physicalStart`
3034 */ 2664 */
3035 _physicalCountVal: 0, 2665 set _virtualStart(val) {
3036 2666 // clamp the value so that _minVirtualStart <= val <= _maxVirtualStart
3037 set _physicalCount(val) { 2667 this._virtualStartVal = Math.min(this._maxVirtualStart, Math.max(this._min VirtualStart, val));
3038 this._physicalCountVal = val; 2668 if (this._physicalCount === 0) {
3039 this._physicalEnd = (this._physicalStart + this._physicalCount - 1) % this ._physicalCount; 2669 this._physicalStart = 0;
3040 }, 2670 this._physicalEnd = 0;
3041 2671 } else {
3042 get _physicalCount() { 2672 this._physicalStart = this._virtualStartVal % this._physicalCount;
3043 return this._physicalCountVal; 2673 this._physicalEnd = (this._physicalStart + this._physicalCount - 1) % th is._physicalCount;
2674 }
3044 }, 2675 },
3045 2676
3046 /** 2677 /**
3047 * The k-th tile that is at the bottom of the scrolling list. 2678 * Gets the n-th item rendered in `_physicalStart`
3048 */ 2679 */
3049 _physicalEnd: 0, 2680 get _virtualStart() {
2681 return this._virtualStartVal;
2682 },
3050 2683
3051 /** 2684 /**
3052 * An optimal physical size such that we will have enough physical items 2685 * An optimal physical size such that we will have enough physical items
3053 * to fill up the viewport and recycle when the user scrolls. 2686 * to fill up the viewport and recycle when the user scrolls.
3054 * 2687 *
3055 * This default value assumes that we will at least have the equivalent 2688 * This default value assumes that we will at least have the equivalent
3056 * to a viewport of physical items above and below the user's viewport. 2689 * to a viewport of physical items above and below the user's viewport.
3057 */ 2690 */
3058 get _optPhysicalSize() { 2691 get _optPhysicalSize() {
3059 return this._viewportSize * this._maxPages; 2692 return this._viewportSize * this._maxPages;
3060 }, 2693 },
3061 2694
3062 /** 2695 /**
3063 * True if the current list is visible. 2696 * True if the current list is visible.
3064 */ 2697 */
3065 get _isVisible() { 2698 get _isVisible() {
3066 return this.scrollTarget && Boolean(this.scrollTarget.offsetWidth || this. scrollTarget.offsetHeight); 2699 return this.scrollTarget && Boolean(this.scrollTarget.offsetWidth || this. scrollTarget.offsetHeight);
3067 }, 2700 },
3068 2701
3069 /** 2702 /**
3070 * Gets the index of the first visible item in the viewport. 2703 * Gets the index of the first visible item in the viewport.
3071 * 2704 *
3072 * @type {number} 2705 * @type {number}
3073 */ 2706 */
3074 get firstVisibleIndex() { 2707 get firstVisibleIndex() {
3075 if (this._firstVisibleIndexVal === null) { 2708 if (this._firstVisibleIndexVal === null) {
3076 var physicalOffset = this._physicalTop + this._scrollerPaddingTop; 2709 var physicalOffset = this._physicalTop;
3077 2710
3078 this._firstVisibleIndexVal = this._iterateItems( 2711 this._firstVisibleIndexVal = this._iterateItems(
3079 function(pidx, vidx) { 2712 function(pidx, vidx) {
3080 physicalOffset += this._physicalSizes[pidx]; 2713 physicalOffset += this._physicalSizes[pidx];
2714
3081 if (physicalOffset > this._scrollPosition) { 2715 if (physicalOffset > this._scrollPosition) {
3082 return vidx; 2716 return vidx;
3083 } 2717 }
3084 }) || 0; 2718 }) || 0;
3085 } 2719 }
3086 return this._firstVisibleIndexVal; 2720 return this._firstVisibleIndexVal;
3087 }, 2721 },
3088 2722
3089 /** 2723 /**
3090 * Gets the index of the last visible item in the viewport. 2724 * Gets the index of the last visible item in the viewport.
3091 * 2725 *
3092 * @type {number} 2726 * @type {number}
3093 */ 2727 */
3094 get lastVisibleIndex() { 2728 get lastVisibleIndex() {
3095 if (this._lastVisibleIndexVal === null) { 2729 if (this._lastVisibleIndexVal === null) {
3096 var physicalOffset = this._physicalTop; 2730 var physicalOffset = this._physicalTop;
3097 2731
3098 this._iterateItems(function(pidx, vidx) { 2732 this._iterateItems(function(pidx, vidx) {
3099 physicalOffset += this._physicalSizes[pidx]; 2733 physicalOffset += this._physicalSizes[pidx];
3100 2734
3101 if (physicalOffset <= this._scrollBottom) { 2735 if(physicalOffset <= this._scrollBottom) {
3102 this._lastVisibleIndexVal = vidx; 2736 this._lastVisibleIndexVal = vidx;
3103 } 2737 }
3104 }); 2738 });
3105 } 2739 }
3106 return this._lastVisibleIndexVal; 2740 return this._lastVisibleIndexVal;
3107 }, 2741 },
3108 2742
3109 get _defaultScrollTarget() {
3110 return this;
3111 },
3112
3113 ready: function() { 2743 ready: function() {
3114 this.addEventListener('focus', this._didFocus.bind(this), true); 2744 this.addEventListener('focus', this._didFocus.bind(this), true);
3115 }, 2745 },
3116 2746
3117 attached: function() { 2747 attached: function() {
3118 this.updateViewportBoundaries(); 2748 this.updateViewportBoundaries();
3119 this._render(); 2749 this._render();
3120 }, 2750 },
3121 2751
3122 detached: function() { 2752 detached: function() {
3123 this._itemsRendered = false; 2753 this._itemsRendered = false;
3124 }, 2754 },
3125 2755
2756 get _defaultScrollTarget() {
2757 return this;
2758 },
2759
3126 /** 2760 /**
3127 * Set the overflow property if this element has its own scrolling region 2761 * Set the overflow property if this element has its own scrolling region
3128 */ 2762 */
3129 _setOverflow: function(scrollTarget) { 2763 _setOverflow: function(scrollTarget) {
3130 this.style.webkitOverflowScrolling = scrollTarget === this ? 'touch' : ''; 2764 this.style.webkitOverflowScrolling = scrollTarget === this ? 'touch' : '';
3131 this.style.overflow = scrollTarget === this ? 'auto' : ''; 2765 this.style.overflow = scrollTarget === this ? 'auto' : '';
3132 }, 2766 },
3133 2767
3134 /** 2768 /**
3135 * Invoke this method if you dynamically update the viewport's 2769 * Invoke this method if you dynamically update the viewport's
3136 * size or CSS padding. 2770 * size or CSS padding.
3137 * 2771 *
3138 * @method updateViewportBoundaries 2772 * @method updateViewportBoundaries
3139 */ 2773 */
3140 updateViewportBoundaries: function() { 2774 updateViewportBoundaries: function() {
3141 this._scrollerPaddingTop = this.scrollTarget === this ? 0 : 2775 var scrollerStyle = window.getComputedStyle(this.scrollTarget);
3142 parseInt(window.getComputedStyle(this)['padding-top'], 10); 2776 this._scrollerPaddingTop = parseInt(scrollerStyle['padding-top'], 10);
3143
3144 this._viewportSize = this._scrollTargetHeight; 2777 this._viewportSize = this._scrollTargetHeight;
3145 }, 2778 },
3146 2779
3147 /** 2780 /**
3148 * Update the models, the position of the 2781 * Update the models, the position of the
3149 * items in the viewport and recycle tiles as needed. 2782 * items in the viewport and recycle tiles as needed.
3150 */ 2783 */
3151 _scrollHandler: function() { 2784 _scrollHandler: function() {
3152 // clamp the `scrollTop` value 2785 // clamp the `scrollTop` value
2786 // IE 10|11 scrollTop may go above `_maxScrollTop`
2787 // iOS `scrollTop` may go below 0 and above `_maxScrollTop`
3153 var scrollTop = Math.max(0, Math.min(this._maxScrollTop, this._scrollTop)) ; 2788 var scrollTop = Math.max(0, Math.min(this._maxScrollTop, this._scrollTop)) ;
3154 var delta = scrollTop - this._scrollPosition;
3155 var tileHeight, tileTop, kth, recycledTileSet, scrollBottom, physicalBotto m; 2789 var tileHeight, tileTop, kth, recycledTileSet, scrollBottom, physicalBotto m;
3156 var ratio = this._ratio; 2790 var ratio = this._ratio;
2791 var delta = scrollTop - this._scrollPosition;
3157 var recycledTiles = 0; 2792 var recycledTiles = 0;
3158 var hiddenContentSize = this._hiddenContentSize; 2793 var hiddenContentSize = this._hiddenContentSize;
3159 var currentRatio = ratio; 2794 var currentRatio = ratio;
3160 var movingUp = []; 2795 var movingUp = [];
3161 2796
3162 // track the last `scrollTop` 2797 // track the last `scrollTop`
3163 this._scrollPosition = scrollTop; 2798 this._scrollPosition = scrollTop;
3164 2799
3165 // clear cached visible indexes 2800 // clear cached visible index
3166 this._firstVisibleIndexVal = null; 2801 this._firstVisibleIndexVal = null;
3167 this._lastVisibleIndexVal = null; 2802 this._lastVisibleIndexVal = null;
3168 2803
3169 scrollBottom = this._scrollBottom; 2804 scrollBottom = this._scrollBottom;
3170 physicalBottom = this._physicalBottom; 2805 physicalBottom = this._physicalBottom;
3171 2806
3172 // random access 2807 // random access
3173 if (Math.abs(delta) > this._physicalSize) { 2808 if (Math.abs(delta) > this._physicalSize) {
3174 this._physicalTop += delta; 2809 this._physicalTop += delta;
3175 recycledTiles = Math.round(delta / this._physicalAverage); 2810 recycledTiles = Math.round(delta / this._physicalAverage);
(...skipping 66 matching lines...) Expand 10 before | Expand all | Expand 10 after
3242 2877
3243 if (recycledTiles === 0) { 2878 if (recycledTiles === 0) {
3244 // If the list ever reach this case, the physical average is not signifi cant enough 2879 // If the list ever reach this case, the physical average is not signifi cant enough
3245 // to create all the items needed to cover the entire viewport. 2880 // to create all the items needed to cover the entire viewport.
3246 // e.g. A few items have a height that differs from the average by serve ral order of magnitude. 2881 // e.g. A few items have a height that differs from the average by serve ral order of magnitude.
3247 if (physicalBottom < scrollBottom || this._physicalTop > scrollTop) { 2882 if (physicalBottom < scrollBottom || this._physicalTop > scrollTop) {
3248 this.async(this._increasePool.bind(this, 1)); 2883 this.async(this._increasePool.bind(this, 1));
3249 } 2884 }
3250 } else { 2885 } else {
3251 this._virtualStart = this._virtualStart + recycledTiles; 2886 this._virtualStart = this._virtualStart + recycledTiles;
3252 this._physicalStart = this._physicalStart + recycledTiles;
3253 this._update(recycledTileSet, movingUp); 2887 this._update(recycledTileSet, movingUp);
3254 } 2888 }
3255 }, 2889 },
3256 2890
3257 /** 2891 /**
3258 * Update the list of items, starting from the `_virtualStart` item. 2892 * Update the list of items, starting from the `_virtualStart` item.
3259 * @param {!Array<number>=} itemSet 2893 * @param {!Array<number>=} itemSet
3260 * @param {!Array<number>=} movingUp 2894 * @param {!Array<number>=} movingUp
3261 */ 2895 */
3262 _update: function(itemSet, movingUp) { 2896 _update: function(itemSet, movingUp) {
3263 // manage focus 2897 // manage focus
3264 this._manageFocus(); 2898 if (this._isIndexRendered(this._focusedIndex)) {
2899 this._restoreFocusedItem();
2900 } else {
2901 this._createFocusBackfillItem();
2902 }
3265 // update models 2903 // update models
3266 this._assignModels(itemSet); 2904 this._assignModels(itemSet);
3267 // measure heights 2905 // measure heights
3268 this._updateMetrics(itemSet); 2906 this._updateMetrics(itemSet);
3269 // adjust offset after measuring 2907 // adjust offset after measuring
3270 if (movingUp) { 2908 if (movingUp) {
3271 while (movingUp.length) { 2909 while (movingUp.length) {
3272 this._physicalTop -= this._physicalSizes[movingUp.pop()]; 2910 this._physicalTop -= this._physicalSizes[movingUp.pop()];
3273 } 2911 }
3274 } 2912 }
(...skipping 46 matching lines...) Expand 10 before | Expand all | Expand 10 after
3321 this._debounceTemplate(this._increasePool.bind(this, 1)); 2959 this._debounceTemplate(this._increasePool.bind(this, 1));
3322 } 2960 }
3323 this._lastPage = currentPage; 2961 this._lastPage = currentPage;
3324 return true; 2962 return true;
3325 }, 2963 },
3326 2964
3327 /** 2965 /**
3328 * Increases the pool size. 2966 * Increases the pool size.
3329 */ 2967 */
3330 _increasePool: function(missingItems) { 2968 _increasePool: function(missingItems) {
2969 // limit the size
3331 var nextPhysicalCount = Math.min( 2970 var nextPhysicalCount = Math.min(
3332 this._physicalCount + missingItems, 2971 this._physicalCount + missingItems,
3333 this._virtualCount - this._virtualStart, 2972 this._virtualCount - this._virtualStart,
3334 MAX_PHYSICAL_COUNT 2973 MAX_PHYSICAL_COUNT
3335 ); 2974 );
3336 var prevPhysicalCount = this._physicalCount; 2975 var prevPhysicalCount = this._physicalCount;
3337 var delta = nextPhysicalCount - prevPhysicalCount; 2976 var delta = nextPhysicalCount - prevPhysicalCount;
3338 2977
3339 if (delta <= 0) { 2978 if (delta > 0) {
3340 return; 2979 [].push.apply(this._physicalItems, this._createPool(delta));
2980 [].push.apply(this._physicalSizes, new Array(delta));
2981
2982 this._physicalCount = prevPhysicalCount + delta;
2983 // tail call
2984 return this._update();
3341 } 2985 }
3342
3343 [].push.apply(this._physicalItems, this._createPool(delta));
3344 [].push.apply(this._physicalSizes, new Array(delta));
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;
3355 }
3356 this._update();
3357 }, 2986 },
3358 2987
3359 /** 2988 /**
3360 * Render a new list of items. This method does exactly the same as `update` , 2989 * Render a new list of items. This method does exactly the same as `update` ,
3361 * but it also ensures that only one `update` cycle is created. 2990 * but it also ensures that only one `update` cycle is created.
3362 */ 2991 */
3363 _render: function() { 2992 _render: function() {
3364 var requiresUpdate = this._virtualCount > 0 || this._physicalCount > 0; 2993 var requiresUpdate = this._virtualCount > 0 || this._physicalCount > 0;
3365 2994
3366 if (this.isAttached && !this._itemsRendered && this._isVisible && requires Update) { 2995 if (this.isAttached && !this._itemsRendered && this._isVisible && requires Update) {
(...skipping 68 matching lines...) Expand 10 before | Expand all | Expand 10 after
3435 _forwardParentPath: function(path, value) { 3064 _forwardParentPath: function(path, value) {
3436 if (this._physicalItems) { 3065 if (this._physicalItems) {
3437 this._physicalItems.forEach(function(item) { 3066 this._physicalItems.forEach(function(item) {
3438 item._templateInstance.notifyPath(path, value, true); 3067 item._templateInstance.notifyPath(path, value, true);
3439 }, this); 3068 }, this);
3440 } 3069 }
3441 }, 3070 },
3442 3071
3443 /** 3072 /**
3444 * Called as a side effect of a host items.<key>.<path> path change, 3073 * Called as a side effect of a host items.<key>.<path> path change,
3445 * responsible for notifying item.<path> changes. 3074 * responsible for notifying item.<path> changes to row for key.
3446 */ 3075 */
3447 _forwardItemPath: function(path, value) { 3076 _forwardItemPath: function(path, value) {
3448 if (!this._physicalIndexForKey) { 3077 if (this._physicalIndexForKey) {
3449 return; 3078 var dot = path.indexOf('.');
3450 } 3079 var key = path.substring(0, dot < 0 ? path.length : dot);
3451 var inst; 3080 var idx = this._physicalIndexForKey[key];
3452 var dot = path.indexOf('.'); 3081 var row = this._physicalItems[idx];
3453 var key = path.substring(0, dot < 0 ? path.length : dot);
3454 var idx = this._physicalIndexForKey[key];
3455 var el = this._physicalItems[idx];
3456 3082
3457 3083 if (idx === this._focusedIndex && this._offscreenFocusedItem) {
3458 if (idx === this._focusedIndex && this._offscreenFocusedItem) { 3084 row = this._offscreenFocusedItem;
3459 el = this._offscreenFocusedItem; 3085 }
3460 } 3086 if (row) {
3461 if (!el) { 3087 var inst = row._templateInstance;
3462 return; 3088 if (dot >= 0) {
3463 } 3089 path = this.as + '.' + path.substring(dot+1);
3464 3090 inst.notifyPath(path, value, true);
3465 inst = el._templateInstance; 3091 } else {
3466 3092 inst[this.as] = value;
3467 if (inst.__key__ !== key) { 3093 }
3468 return; 3094 }
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;
3475 } 3095 }
3476 }, 3096 },
3477 3097
3478 /** 3098 /**
3479 * Called when the items have changed. That is, ressignments 3099 * Called when the items have changed. That is, ressignments
3480 * to `items`, splices or updates to a single item. 3100 * to `items`, splices or updates to a single item.
3481 */ 3101 */
3482 _itemsChanged: function(change) { 3102 _itemsChanged: function(change) {
3483 if (change.path === 'items') { 3103 if (change.path === 'items') {
3484 // reset items 3104
3105 this._restoreFocusedItem();
3106 // render the new set
3107 this._itemsRendered = false;
3108 // update the whole set
3485 this._virtualStart = 0; 3109 this._virtualStart = 0;
3486 this._physicalTop = 0; 3110 this._physicalTop = 0;
3487 this._virtualCount = this.items ? this.items.length : 0; 3111 this._virtualCount = this.items ? this.items.length : 0;
3112 this._focusedIndex = 0;
3488 this._collection = this.items ? Polymer.Collection.get(this.items) : nul l; 3113 this._collection = this.items ? Polymer.Collection.get(this.items) : nul l;
3489 this._physicalIndexForKey = {}; 3114 this._physicalIndexForKey = {};
3490 3115
3491 this._resetScrollPosition(0); 3116 this._resetScrollPosition(0);
3492 this._removeFocusedItem();
3493 3117
3494 // create the initial physical items 3118 // create the initial physical items
3495 if (!this._physicalItems) { 3119 if (!this._physicalItems) {
3496 this._physicalCount = Math.max(1, Math.min(DEFAULT_PHYSICAL_COUNT, thi s._virtualCount)); 3120 this._physicalCount = Math.max(1, Math.min(DEFAULT_PHYSICAL_COUNT, thi s._virtualCount));
3497 this._physicalItems = this._createPool(this._physicalCount); 3121 this._physicalItems = this._createPool(this._physicalCount);
3498 this._physicalSizes = new Array(this._physicalCount); 3122 this._physicalSizes = new Array(this._physicalCount);
3499 } 3123 }
3500 3124 this._debounceTemplate(this._render);
3501 this._physicalStart = 0;
3502 3125
3503 } else if (change.path === 'items.splices') { 3126 } else if (change.path === 'items.splices') {
3127 // render the new set
3128 this._itemsRendered = false;
3504 this._adjustVirtualIndex(change.value.indexSplices); 3129 this._adjustVirtualIndex(change.value.indexSplices);
3505 this._virtualCount = this.items ? this.items.length : 0; 3130 this._virtualCount = this.items ? this.items.length : 0;
3506 3131
3132 this._debounceTemplate(this._render);
3133
3134 if (this._focusedIndex < 0 || this._focusedIndex >= this._virtualCount) {
3135 this._focusedIndex = 0;
3136 }
3137 this._debounceTemplate(this._render);
3138
3507 } else { 3139 } else {
3508 // update a single item 3140 // update a single item
3509 this._forwardItemPath(change.path.split('.').slice(1).join('.'), change. value); 3141 this._forwardItemPath(change.path.split('.').slice(1).join('.'), change. value);
3510 return;
3511 } 3142 }
3512
3513 this._itemsRendered = false;
3514 this._debounceTemplate(this._render);
3515 }, 3143 },
3516 3144
3517 /** 3145 /**
3518 * @param {!Array<!PolymerSplice>} splices 3146 * @param {!Array<!PolymerSplice>} splices
3519 */ 3147 */
3520 _adjustVirtualIndex: function(splices) { 3148 _adjustVirtualIndex: function(splices) {
3521 splices.forEach(function(splice) { 3149 var i, splice, idx;
3150
3151 for (i = 0; i < splices.length; i++) {
3152 splice = splices[i];
3153
3522 // deselect removed items 3154 // deselect removed items
3523 splice.removed.forEach(this._removeItem, this); 3155 splice.removed.forEach(this.$.selector.deselect, this.$.selector);
3156
3157 idx = splice.index;
3524 // We only need to care about changes happening above the current positi on 3158 // We only need to care about changes happening above the current positi on
3525 if (splice.index < this._virtualStart) { 3159 if (idx >= this._virtualStart) {
3526 var delta = Math.max( 3160 break;
3527 splice.addedCount - splice.removed.length, 3161 }
3528 splice.index - this._virtualStart);
3529 3162
3530 this._virtualStart = this._virtualStart + delta; 3163 this._virtualStart = this._virtualStart +
3531 3164 Math.max(splice.addedCount - splice.removed.length, idx - this._virt ualStart);
3532 if (this._focusedIndex >= 0) {
3533 this._focusedIndex = this._focusedIndex + delta;
3534 }
3535 }
3536 }, this);
3537 },
3538
3539 _removeItem: function(item) {
3540 this.$.selector.deselect(item);
3541 // remove the current focused item
3542 if (this._focusedItem && this._focusedItem._templateInstance[this.as] === item) {
3543 this._removeFocusedItem();
3544 } 3165 }
3545 }, 3166 },
3546 3167
3547 /** 3168 /**
3548 * Executes a provided function per every physical index in `itemSet` 3169 * Executes a provided function per every physical index in `itemSet`
3549 * `itemSet` default value is equivalent to the entire set of physical index es. 3170 * `itemSet` default value is equivalent to the entire set of physical index es.
3550 * 3171 *
3551 * @param {!function(number, number)} fn 3172 * @param {!function(number, number)} fn
3552 * @param {!Array<number>=} itemSet 3173 * @param {!Array<number>=} itemSet
3553 */ 3174 */
(...skipping 32 matching lines...) Expand 10 before | Expand all | Expand 10 after
3586 /** 3207 /**
3587 * Assigns the data models to a given set of items. 3208 * Assigns the data models to a given set of items.
3588 * @param {!Array<number>=} itemSet 3209 * @param {!Array<number>=} itemSet
3589 */ 3210 */
3590 _assignModels: function(itemSet) { 3211 _assignModels: function(itemSet) {
3591 this._iterateItems(function(pidx, vidx) { 3212 this._iterateItems(function(pidx, vidx) {
3592 var el = this._physicalItems[pidx]; 3213 var el = this._physicalItems[pidx];
3593 var inst = el._templateInstance; 3214 var inst = el._templateInstance;
3594 var item = this.items && this.items[vidx]; 3215 var item = this.items && this.items[vidx];
3595 3216
3596 if (item != null) { 3217 if (item !== undefined && item !== null) {
3597 inst[this.as] = item; 3218 inst[this.as] = item;
3598 inst.__key__ = this._collection.getKey(item); 3219 inst.__key__ = this._collection.getKey(item);
3599 inst[this.selectedAs] = /** @type {!ArraySelectorElement} */ (this.$.s elector).isSelected(item); 3220 inst[this.selectedAs] = /** @type {!ArraySelectorElement} */ (this.$.s elector).isSelected(item);
3600 inst[this.indexAs] = vidx; 3221 inst[this.indexAs] = vidx;
3601 inst.tabIndex = this._focusedIndex === vidx ? 0 : -1; 3222 inst.tabIndex = vidx === this._focusedIndex ? 0 : -1;
3223 el.removeAttribute('hidden');
3602 this._physicalIndexForKey[inst.__key__] = pidx; 3224 this._physicalIndexForKey[inst.__key__] = pidx;
3603 el.removeAttribute('hidden');
3604 } else { 3225 } else {
3605 inst.__key__ = null; 3226 inst.__key__ = null;
3606 el.setAttribute('hidden', ''); 3227 el.setAttribute('hidden', '');
3607 } 3228 }
3229
3608 }, itemSet); 3230 }, itemSet);
3609 }, 3231 },
3610 3232
3611 /** 3233 /**
3612 * Updates the height for a given set of items. 3234 * Updates the height for a given set of items.
3613 * 3235 *
3614 * @param {!Array<number>=} itemSet 3236 * @param {!Array<number>=} itemSet
3615 */ 3237 */
3616 _updateMetrics: function(itemSet) { 3238 _updateMetrics: function(itemSet) {
3617 // Make sure we distributed all the physical items 3239 // Make sure we distributed all the physical items
(...skipping 27 matching lines...) Expand all
3645 3267
3646 /** 3268 /**
3647 * Updates the position of the physical items. 3269 * Updates the position of the physical items.
3648 */ 3270 */
3649 _positionItems: function() { 3271 _positionItems: function() {
3650 this._adjustScrollPosition(); 3272 this._adjustScrollPosition();
3651 3273
3652 var y = this._physicalTop; 3274 var y = this._physicalTop;
3653 3275
3654 this._iterateItems(function(pidx) { 3276 this._iterateItems(function(pidx) {
3277
3655 this.translate3d(0, y + 'px', 0, this._physicalItems[pidx]); 3278 this.translate3d(0, y + 'px', 0, this._physicalItems[pidx]);
3656 y += this._physicalSizes[pidx]; 3279 y += this._physicalSizes[pidx];
3280
3657 }); 3281 });
3658 }, 3282 },
3659 3283
3660 /** 3284 /**
3661 * Adjusts the scroll position when it was overestimated. 3285 * Adjusts the scroll position when it was overestimated.
3662 */ 3286 */
3663 _adjustScrollPosition: function() { 3287 _adjustScrollPosition: function() {
3664 var deltaHeight = this._virtualStart === 0 ? this._physicalTop : 3288 var deltaHeight = this._virtualStart === 0 ? this._physicalTop :
3665 Math.min(this._scrollPosition + this._physicalTop, 0); 3289 Math.min(this._scrollPosition + this._physicalTop, 0);
3666 3290
(...skipping 27 matching lines...) Expand all
3694 3318
3695 forceUpdate = forceUpdate || this._scrollHeight === 0; 3319 forceUpdate = forceUpdate || this._scrollHeight === 0;
3696 forceUpdate = forceUpdate || this._scrollPosition >= this._estScrollHeight - this._physicalSize; 3320 forceUpdate = forceUpdate || this._scrollPosition >= this._estScrollHeight - this._physicalSize;
3697 3321
3698 // amortize height adjustment, so it won't trigger repaints very often 3322 // amortize height adjustment, so it won't trigger repaints very often
3699 if (forceUpdate || Math.abs(this._estScrollHeight - this._scrollHeight) >= this._optPhysicalSize) { 3323 if (forceUpdate || Math.abs(this._estScrollHeight - this._scrollHeight) >= this._optPhysicalSize) {
3700 this.$.items.style.height = this._estScrollHeight + 'px'; 3324 this.$.items.style.height = this._estScrollHeight + 'px';
3701 this._scrollHeight = this._estScrollHeight; 3325 this._scrollHeight = this._estScrollHeight;
3702 } 3326 }
3703 }, 3327 },
3328
3704 /** 3329 /**
3705 * Scroll to a specific item in the virtual list regardless 3330 * Scroll to a specific item in the virtual list regardless
3706 * of the physical items in the DOM tree. 3331 * of the physical items in the DOM tree.
3707 * 3332 *
3708 * @method scrollToIndex 3333 * @method scrollToIndex
3709 * @param {number} idx The index of the item 3334 * @param {number} idx The index of the item
3710 */ 3335 */
3711 scrollToIndex: function(idx) { 3336 scrollToIndex: function(idx) {
3712 if (typeof idx !== 'number') { 3337 if (typeof idx !== 'number') {
3713 return; 3338 return;
3714 } 3339 }
3715 3340
3716 Polymer.dom.flush(); 3341 Polymer.dom.flush();
3717 3342
3343 var firstVisible = this.firstVisibleIndex;
3718 idx = Math.min(Math.max(idx, 0), this._virtualCount-1); 3344 idx = Math.min(Math.max(idx, 0), this._virtualCount-1);
3719 // update the virtual start only when needed 3345
3720 if (!this._isIndexRendered(idx) || idx >= this._maxVirtualStart) { 3346 // start at the previous virtual item
3721 this._virtualStart = idx - 1; 3347 // so we have a item above the first visible item
3722 } 3348 this._virtualStart = idx - 1;
3723 // manage focus
3724 this._manageFocus();
3725 // assign new models 3349 // assign new models
3726 this._assignModels(); 3350 this._assignModels();
3727 // measure the new sizes 3351 // measure the new sizes
3728 this._updateMetrics(); 3352 this._updateMetrics();
3729 // estimate new physical offset 3353 // estimate new physical offset
3730 this._physicalTop = this._virtualStart * this._physicalAverage; 3354 this._physicalTop = this._virtualStart * this._physicalAverage;
3731 3355
3732 var currentTopItem = this._physicalStart; 3356 var currentTopItem = this._physicalStart;
3733 var currentVirtualItem = this._virtualStart; 3357 var currentVirtualItem = this._virtualStart;
3734 var targetOffsetTop = 0; 3358 var targetOffsetTop = 0;
(...skipping 42 matching lines...) Expand 10 before | Expand all | Expand 10 after
3777 this.updateViewportBoundaries(); 3401 this.updateViewportBoundaries();
3778 this.scrollToIndex(this.firstVisibleIndex); 3402 this.scrollToIndex(this.firstVisibleIndex);
3779 } 3403 }
3780 }); 3404 });
3781 }, 3405 },
3782 3406
3783 _getModelFromItem: function(item) { 3407 _getModelFromItem: function(item) {
3784 var key = this._collection.getKey(item); 3408 var key = this._collection.getKey(item);
3785 var pidx = this._physicalIndexForKey[key]; 3409 var pidx = this._physicalIndexForKey[key];
3786 3410
3787 if (pidx != null) { 3411 if (pidx !== undefined) {
3788 return this._physicalItems[pidx]._templateInstance; 3412 return this._physicalItems[pidx]._templateInstance;
3789 } 3413 }
3790 return null; 3414 return null;
3791 }, 3415 },
3792 3416
3793 /** 3417 /**
3794 * Gets a valid item instance from its index or the object value. 3418 * Gets a valid item instance from its index or the object value.
3795 * 3419 *
3796 * @param {(Object|number)} item The item object or its index 3420 * @param {(Object|number)} item The item object or its index
3797 */ 3421 */
(...skipping 117 matching lines...) Expand 10 before | Expand all | Expand 10 after
3915 * Updates the size of an item. 3539 * Updates the size of an item.
3916 * 3540 *
3917 * @method updateSizeForItem 3541 * @method updateSizeForItem
3918 * @param {(Object|number)} item The item object or its index 3542 * @param {(Object|number)} item The item object or its index
3919 */ 3543 */
3920 updateSizeForItem: function(item) { 3544 updateSizeForItem: function(item) {
3921 item = this._getNormalizedItem(item); 3545 item = this._getNormalizedItem(item);
3922 var key = this._collection.getKey(item); 3546 var key = this._collection.getKey(item);
3923 var pidx = this._physicalIndexForKey[key]; 3547 var pidx = this._physicalIndexForKey[key];
3924 3548
3925 if (pidx != null) { 3549 if (pidx !== undefined) {
3926 this._updateMetrics([pidx]); 3550 this._updateMetrics([pidx]);
3927 this._positionItems(); 3551 this._positionItems();
3928 } 3552 }
3929 }, 3553 },
3930 3554
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
3956 _isIndexRendered: function(idx) { 3555 _isIndexRendered: function(idx) {
3957 return idx >= this._virtualStart && idx <= this._virtualEnd; 3556 return idx >= this._virtualStart && idx <= this._virtualEnd;
3958 }, 3557 },
3959 3558
3960 _isIndexVisible: function(idx) { 3559 _getPhysicalItemForIndex: function(idx, force) {
3961 return idx >= this.firstVisibleIndex && idx <= this.lastVisibleIndex; 3560 if (!this._collection) {
3962 }, 3561 return null;
3963 3562 }
3964 _getPhysicalIndex: function(idx) { 3563 if (!this._isIndexRendered(idx)) {
3965 return this._physicalIndexForKey[this._collection.getKey(this._getNormaliz edItem(idx))]; 3564 if (force) {
3565 this.scrollToIndex(idx);
3566 return this._getPhysicalItemForIndex(idx, false);
3567 }
3568 return null;
3569 }
3570 var item = this._getNormalizedItem(idx);
3571 var physicalItem = this._physicalItems[this._physicalIndexForKey[this._col lection.getKey(item)]];
3572
3573 return physicalItem || null;
3966 }, 3574 },
3967 3575
3968 _focusPhysicalItem: function(idx) { 3576 _focusPhysicalItem: function(idx) {
3969 if (idx < 0 || idx >= this._virtualCount) { 3577 this._restoreFocusedItem();
3578
3579 var physicalItem = this._getPhysicalItemForIndex(idx, true);
3580 if (!physicalItem) {
3970 return; 3581 return;
3971 } 3582 }
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)];
3979 var SECRET = ~(Math.random() * 100); 3583 var SECRET = ~(Math.random() * 100);
3980 var model = physicalItem._templateInstance; 3584 var model = physicalItem._templateInstance;
3981 var focusable; 3585 var focusable;
3982 3586
3983 // set a secret tab index
3984 model.tabIndex = SECRET; 3587 model.tabIndex = SECRET;
3985 // check if focusable element is the physical item 3588 // the focusable element could be the entire physical item
3986 if (physicalItem.tabIndex === SECRET) { 3589 if (physicalItem.tabIndex === SECRET) {
3987 focusable = physicalItem; 3590 focusable = physicalItem;
3988 } 3591 }
3989 // search for the element which tabindex is bound to the secret tab index 3592 // the focusable element could be somewhere within the physical item
3990 if (!focusable) { 3593 if (!focusable) {
3991 focusable = Polymer.dom(physicalItem).querySelector('[tabindex="' + SECR ET + '"]'); 3594 focusable = Polymer.dom(physicalItem).querySelector('[tabindex="' + SECR ET + '"]');
3992 } 3595 }
3993 // restore the tab index 3596 // restore the tab index
3994 model.tabIndex = 0; 3597 model.tabIndex = 0;
3995 // focus the focusable element
3996 this._focusedIndex = idx;
3997 focusable && focusable.focus(); 3598 focusable && focusable.focus();
3998 }, 3599 },
3999 3600
3601 _restoreFocusedItem: function() {
3602 if (!this._offscreenFocusedItem) {
3603 return;
3604 }
3605 var item = this._getNormalizedItem(this._focusedIndex);
3606 var pidx = this._physicalIndexForKey[this._collection.getKey(item)];
3607
3608 if (pidx !== undefined) {
3609 this.translate3d(0, HIDDEN_Y, 0, this._physicalItems[pidx]);
3610 this._physicalItems[pidx] = this._offscreenFocusedItem;
3611 }
3612 this._offscreenFocusedItem = null;
3613 },
3614
4000 _removeFocusedItem: function() { 3615 _removeFocusedItem: function() {
4001 if (this._offscreenFocusedItem) { 3616 if (!this._offscreenFocusedItem) {
4002 Polymer.dom(this).removeChild(this._offscreenFocusedItem); 3617 return;
4003 } 3618 }
3619 Polymer.dom(this).removeChild(this._offscreenFocusedItem);
4004 this._offscreenFocusedItem = null; 3620 this._offscreenFocusedItem = null;
4005 this._focusBackfillItem = null; 3621 this._focusBackfillItem = null;
4006 this._focusedItem = null;
4007 this._focusedIndex = -1;
4008 }, 3622 },
4009 3623
4010 _createFocusBackfillItem: function() { 3624 _createFocusBackfillItem: function() {
4011 var pidx, fidx = this._focusedIndex; 3625 if (this._offscreenFocusedItem) {
4012 if (this._offscreenFocusedItem || fidx < 0) {
4013 return; 3626 return;
4014 } 3627 }
3628 var item = this._getNormalizedItem(this._focusedIndex);
3629 var pidx = this._physicalIndexForKey[this._collection.getKey(item)];
3630
3631 this._offscreenFocusedItem = this._physicalItems[pidx];
3632 this.translate3d(0, HIDDEN_Y, 0, this._offscreenFocusedItem);
3633
4015 if (!this._focusBackfillItem) { 3634 if (!this._focusBackfillItem) {
4016 // create a physical item, so that it backfills the focused item.
4017 var stampedTemplate = this.stamp(null); 3635 var stampedTemplate = this.stamp(null);
4018 this._focusBackfillItem = stampedTemplate.root.querySelector('*'); 3636 this._focusBackfillItem = stampedTemplate.root.querySelector('*');
4019 Polymer.dom(this).appendChild(stampedTemplate.root); 3637 Polymer.dom(this).appendChild(stampedTemplate.root);
4020 } 3638 }
4021 // get the physical index for the focused index 3639 this._physicalItems[pidx] = this._focusBackfillItem;
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 }
4055 }, 3640 },
4056 3641
4057 _didFocus: function(e) { 3642 _didFocus: function(e) {
4058 var targetModel = this.modelForElement(e.target); 3643 var targetModel = this.modelForElement(e.target);
4059 var focusedModel = this._focusedItem ? this._focusedItem._templateInstance : null;
4060 var hasOffscreenFocusedItem = this._offscreenFocusedItem !== null;
4061 var fidx = this._focusedIndex; 3644 var fidx = this._focusedIndex;
4062 3645
4063 if (!targetModel || !focusedModel) { 3646 if (!targetModel) {
4064 return; 3647 return;
4065 } 3648 }
4066 if (focusedModel === targetModel) { 3649 this._restoreFocusedItem();
4067 // if the user focused the same item, then bring it into view if it's no t visible 3650
4068 if (!this._isIndexVisible(fidx)) { 3651 if (this.modelForElement(this._offscreenFocusedItem) === targetModel) {
4069 this.scrollToIndex(fidx); 3652 this.scrollToIndex(fidx);
4070 }
4071 } else { 3653 } else {
4072 this._restoreFocusedItem();
4073 // restore tabIndex for the currently focused item 3654 // restore tabIndex for the currently focused item
4074 focusedModel.tabIndex = -1; 3655 this._getModelFromItem(this._getNormalizedItem(fidx)).tabIndex = -1;
4075 // set the tabIndex for the next focused item 3656 // set the tabIndex for the next focused item
4076 targetModel.tabIndex = 0; 3657 targetModel.tabIndex = 0;
4077 fidx = targetModel[this.indexAs]; 3658 fidx = /** @type {{index: number}} */(targetModel).index;
4078 this._focusedIndex = fidx; 3659 this._focusedIndex = fidx;
4079 this._focusedItem = this._physicalItems[this._getPhysicalIndex(fidx)]; 3660 // bring the item into view
4080 3661 if (fidx < this.firstVisibleIndex || fidx > this.lastVisibleIndex) {
4081 if (hasOffscreenFocusedItem && !this._offscreenFocusedItem) { 3662 this.scrollToIndex(fidx);
3663 } else {
4082 this._update(); 3664 this._update();
4083 } 3665 }
4084 } 3666 }
4085 }, 3667 },
4086 3668
4087 _didMoveUp: function() { 3669 _didMoveUp: function() {
4088 this._focusPhysicalItem(this._focusedIndex - 1); 3670 this._focusPhysicalItem(Math.max(0, this._focusedIndex - 1));
4089 }, 3671 },
4090 3672
4091 _didMoveDown: function() { 3673 _didMoveDown: function() {
4092 this._focusPhysicalItem(this._focusedIndex + 1); 3674 this._focusPhysicalItem(Math.min(this._virtualCount, this._focusedIndex + 1));
4093 }, 3675 },
4094 3676
4095 _didEnter: function(e) { 3677 _didEnter: function(e) {
3678 // focus the currently focused physical item
4096 this._focusPhysicalItem(this._focusedIndex); 3679 this._focusPhysicalItem(this._focusedIndex);
4097 this._selectionHandler(e.detail.keyboardEvent); 3680 // toggle selection
3681 this._selectionHandler(/** @type {{keyboardEvent: Event}} */(e.detail).key boardEvent);
4098 } 3682 }
4099 }); 3683 });
4100 3684
4101 })(); 3685 })();
3686 // Copyright (c) 2013 The Chromium Authors. All rights reserved.
3687 // Use of this source code is governed by a BSD-style license that can be
3688 // found in the LICENSE file.
3689
3690 /**
3691 * @fileoverview Assertion support.
3692 */
3693
3694 /**
3695 * Verify |condition| is truthy and return |condition| if so.
3696 * @template T
3697 * @param {T} condition A condition to check for truthiness. Note that this
3698 * may be used to test whether a value is defined or not, and we don't want
3699 * to force a cast to Boolean.
3700 * @param {string=} opt_message A message to show on failure.
3701 * @return {T} A non-null |condition|.
3702 */
3703 function assert(condition, opt_message) {
3704 if (!condition) {
3705 var message = 'Assertion failed';
3706 if (opt_message)
3707 message = message + ': ' + opt_message;
3708 var error = new Error(message);
3709 var global = function() { return this; }();
3710 if (global.traceAssertionsForTesting)
3711 console.warn(error.stack);
3712 throw error;
3713 }
3714 return condition;
3715 }
3716
3717 /**
3718 * Call this from places in the code that should never be reached.
3719 *
3720 * For example, handling all the values of enum with a switch() like this:
3721 *
3722 * function getValueFromEnum(enum) {
3723 * switch (enum) {
3724 * case ENUM_FIRST_OF_TWO:
3725 * return first
3726 * case ENUM_LAST_OF_TWO:
3727 * return last;
3728 * }
3729 * assertNotReached();
3730 * return document;
3731 * }
3732 *
3733 * This code should only be hit in the case of serious programmer error or
3734 * unexpected input.
3735 *
3736 * @param {string=} opt_message A message to show when this is hit.
3737 */
3738 function assertNotReached(opt_message) {
3739 assert(false, opt_message || 'Unreachable code hit');
3740 }
3741
3742 /**
3743 * @param {*} value The value to check.
3744 * @param {function(new: T, ...)} type A user-defined constructor.
3745 * @param {string=} opt_message A message to show when this is hit.
3746 * @return {T}
3747 * @template T
3748 */
3749 function assertInstanceof(value, type, opt_message) {
3750 // We don't use assert immediately here so that we avoid constructing an error
3751 // message if we don't have to.
3752 if (!(value instanceof type)) {
3753 assertNotReached(opt_message || 'Value ' + value +
3754 ' is not a[n] ' + (type.name || typeof type));
3755 }
3756 return value;
3757 };
3758 // Copyright 2015 The Chromium Authors. All rights reserved.
3759 // Use of this source code is governed by a BSD-style license that can be
3760 // found in the LICENSE file.
3761
3762 cr.define('downloads', function() {
3763 /**
3764 * @param {string} chromeSendName
3765 * @return {function(string):void} A chrome.send() callback with curried name.
3766 */
3767 function chromeSendWithId(chromeSendName) {
3768 return function(id) { chrome.send(chromeSendName, [id]); };
3769 }
3770
3771 /** @constructor */
3772 function ActionService() {
3773 /** @private {Array<string>} */
3774 this.searchTerms_ = [];
3775 }
3776
3777 /**
3778 * @param {string} s
3779 * @return {string} |s| without whitespace at the beginning or end.
3780 */
3781 function trim(s) { return s.trim(); }
3782
3783 /**
3784 * @param {string|undefined} value
3785 * @return {boolean} Whether |value| is truthy.
3786 */
3787 function truthy(value) { return !!value; }
3788
3789 /**
3790 * @param {string} searchText Input typed by the user into a search box.
3791 * @return {Array<string>} A list of terms extracted from |searchText|.
3792 */
3793 ActionService.splitTerms = function(searchText) {
3794 // Split quoted terms (e.g., 'The "lazy" dog' => ['The', 'lazy', 'dog']).
3795 return searchText.split(/"([^"]*)"/).map(trim).filter(truthy);
3796 };
3797
3798 ActionService.prototype = {
3799 /** @param {string} id ID of the download to cancel. */
3800 cancel: chromeSendWithId('cancel'),
3801
3802 /** Instructs the browser to clear all finished downloads. */
3803 clearAll: function() {
3804 if (loadTimeData.getBoolean('allowDeletingHistory')) {
3805 chrome.send('clearAll');
3806 this.search('');
3807 }
3808 },
3809
3810 /** @param {string} id ID of the dangerous download to discard. */
3811 discardDangerous: chromeSendWithId('discardDangerous'),
3812
3813 /** @param {string} url URL of a file to download. */
3814 download: function(url) {
3815 var a = document.createElement('a');
3816 a.href = url;
3817 a.setAttribute('download', '');
3818 a.click();
3819 },
3820
3821 /** @param {string} id ID of the download that the user started dragging. */
3822 drag: chromeSendWithId('drag'),
3823
3824 /** Loads more downloads with the current search terms. */
3825 loadMore: function() {
3826 chrome.send('getDownloads', this.searchTerms_);
3827 },
3828
3829 /**
3830 * @return {boolean} Whether the user is currently searching for downloads
3831 * (i.e. has a non-empty search term).
3832 */
3833 isSearching: function() {
3834 return this.searchTerms_.length > 0;
3835 },
3836
3837 /** Opens the current local destination for downloads. */
3838 openDownloadsFolder: chrome.send.bind(chrome, 'openDownloadsFolder'),
3839
3840 /**
3841 * @param {string} id ID of the download to run locally on the user's box.
3842 */
3843 openFile: chromeSendWithId('openFile'),
3844
3845 /** @param {string} id ID the of the progressing download to pause. */
3846 pause: chromeSendWithId('pause'),
3847
3848 /** @param {string} id ID of the finished download to remove. */
3849 remove: chromeSendWithId('remove'),
3850
3851 /** @param {string} id ID of the paused download to resume. */
3852 resume: chromeSendWithId('resume'),
3853
3854 /**
3855 * @param {string} id ID of the dangerous download to save despite
3856 * warnings.
3857 */
3858 saveDangerous: chromeSendWithId('saveDangerous'),
3859
3860 /** @param {string} searchText What to search for. */
3861 search: function(searchText) {
3862 var searchTerms = ActionService.splitTerms(searchText);
3863 var sameTerms = searchTerms.length == this.searchTerms_.length;
3864
3865 for (var i = 0; sameTerms && i < searchTerms.length; ++i) {
3866 if (searchTerms[i] != this.searchTerms_[i])
3867 sameTerms = false;
3868 }
3869
3870 if (sameTerms)
3871 return;
3872
3873 this.searchTerms_ = searchTerms;
3874 this.loadMore();
3875 },
3876
3877 /**
3878 * Shows the local folder a finished download resides in.
3879 * @param {string} id ID of the download to show.
3880 */
3881 show: chromeSendWithId('show'),
3882
3883 /** Undo download removal. */
3884 undo: chrome.send.bind(chrome, 'undo'),
3885 };
3886
3887 cr.addSingletonGetter(ActionService);
3888
3889 return {ActionService: ActionService};
3890 });
3891 // Copyright 2015 The Chromium Authors. All rights reserved.
3892 // Use of this source code is governed by a BSD-style license that can be
3893 // found in the LICENSE file.
3894
3895 cr.define('downloads', function() {
3896 /**
3897 * Explains why a download is in DANGEROUS state.
3898 * @enum {string}
3899 */
3900 var DangerType = {
3901 NOT_DANGEROUS: 'NOT_DANGEROUS',
3902 DANGEROUS_FILE: 'DANGEROUS_FILE',
3903 DANGEROUS_URL: 'DANGEROUS_URL',
3904 DANGEROUS_CONTENT: 'DANGEROUS_CONTENT',
3905 UNCOMMON_CONTENT: 'UNCOMMON_CONTENT',
3906 DANGEROUS_HOST: 'DANGEROUS_HOST',
3907 POTENTIALLY_UNWANTED: 'POTENTIALLY_UNWANTED',
3908 };
3909
3910 /**
3911 * The states a download can be in. These correspond to states defined in
3912 * DownloadsDOMHandler::CreateDownloadItemValue
3913 * @enum {string}
3914 */
3915 var States = {
3916 IN_PROGRESS: 'IN_PROGRESS',
3917 CANCELLED: 'CANCELLED',
3918 COMPLETE: 'COMPLETE',
3919 PAUSED: 'PAUSED',
3920 DANGEROUS: 'DANGEROUS',
3921 INTERRUPTED: 'INTERRUPTED',
3922 };
3923
3924 return {
3925 DangerType: DangerType,
3926 States: States,
3927 };
3928 });
3929 // Copyright 2014 The Chromium Authors. All rights reserved.
3930 // Use of this source code is governed by a BSD-style license that can be
3931 // found in the LICENSE file.
3932
3933 // Action links are elements that are used to perform an in-page navigation or
3934 // action (e.g. showing a dialog).
3935 //
3936 // They look like normal anchor (<a>) tags as their text color is blue. However,
3937 // they're subtly different as they're not initially underlined (giving users a
3938 // clue that underlined links navigate while action links don't).
3939 //
3940 // Action links look very similar to normal links when hovered (hand cursor,
3941 // underlined). This gives the user an idea that clicking this link will do
3942 // something similar to navigation but in the same page.
3943 //
3944 // They can be created in JavaScript like this:
3945 //
3946 // var link = document.createElement('a', 'action-link'); // Note second arg.
3947 //
3948 // or with a constructor like this:
3949 //
3950 // var link = new ActionLink();
3951 //
3952 // They can be used easily from HTML as well, like so:
3953 //
3954 // <a is="action-link">Click me!</a>
3955 //
3956 // NOTE: <action-link> and document.createElement('action-link') don't work.
3957
3958 /**
3959 * @constructor
3960 * @extends {HTMLAnchorElement}
3961 */
3962 var ActionLink = document.registerElement('action-link', {
3963 prototype: {
3964 __proto__: HTMLAnchorElement.prototype,
3965
3966 /** @this {ActionLink} */
3967 createdCallback: function() {
3968 // Action links can start disabled (e.g. <a is="action-link" disabled>).
3969 this.tabIndex = this.disabled ? -1 : 0;
3970
3971 if (!this.hasAttribute('role'))
3972 this.setAttribute('role', 'link');
3973
3974 this.addEventListener('keydown', function(e) {
3975 if (!this.disabled && e.keyIdentifier == 'Enter' && !this.href) {
3976 // Schedule a click asynchronously because other 'keydown' handlers
3977 // may still run later (e.g. document.addEventListener('keydown')).
3978 // Specifically options dialogs break when this timeout isn't here.
3979 // NOTE: this affects the "trusted" state of the ensuing click. I
3980 // haven't found anything that breaks because of this (yet).
3981 window.setTimeout(this.click.bind(this), 0);
3982 }
3983 });
3984
3985 function preventDefault(e) {
3986 e.preventDefault();
3987 }
3988
3989 function removePreventDefault() {
3990 document.removeEventListener('selectstart', preventDefault);
3991 document.removeEventListener('mouseup', removePreventDefault);
3992 }
3993
3994 this.addEventListener('mousedown', function() {
3995 // This handlers strives to match the behavior of <a href="...">.
3996
3997 // While the mouse is down, prevent text selection from dragging.
3998 document.addEventListener('selectstart', preventDefault);
3999 document.addEventListener('mouseup', removePreventDefault);
4000
4001 // If focus started via mouse press, don't show an outline.
4002 if (document.activeElement != this)
4003 this.classList.add('no-outline');
4004 });
4005
4006 this.addEventListener('blur', function() {
4007 this.classList.remove('no-outline');
4008 });
4009 },
4010
4011 /** @type {boolean} */
4012 set disabled(disabled) {
4013 if (disabled)
4014 HTMLAnchorElement.prototype.setAttribute.call(this, 'disabled', '');
4015 else
4016 HTMLAnchorElement.prototype.removeAttribute.call(this, 'disabled');
4017 this.tabIndex = disabled ? -1 : 0;
4018 },
4019 get disabled() {
4020 return this.hasAttribute('disabled');
4021 },
4022
4023 /** @override */
4024 setAttribute: function(attr, val) {
4025 if (attr.toLowerCase() == 'disabled')
4026 this.disabled = true;
4027 else
4028 HTMLAnchorElement.prototype.setAttribute.apply(this, arguments);
4029 },
4030
4031 /** @override */
4032 removeAttribute: function(attr) {
4033 if (attr.toLowerCase() == 'disabled')
4034 this.disabled = false;
4035 else
4036 HTMLAnchorElement.prototype.removeAttribute.apply(this, arguments);
4037 },
4038 },
4039
4040 extends: 'a',
4041 });
4102 (function() { 4042 (function() {
4103 4043
4104 // monostate data 4044 // monostate data
4105 var metaDatas = {}; 4045 var metaDatas = {};
4106 var metaArrays = {}; 4046 var metaArrays = {};
4107 var singleton = null; 4047 var singleton = null;
4108 4048
4109 Polymer.IronMeta = Polymer({ 4049 Polymer.IronMeta = Polymer({
4110 4050
4111 is: 'iron-meta', 4051 is: 'iron-meta',
(...skipping 295 matching lines...) Expand 10 before | Expand all | Expand 10 after
4407 */ 4347 */
4408 src: { 4348 src: {
4409 type: String, 4349 type: String,
4410 observer: '_srcChanged' 4350 observer: '_srcChanged'
4411 }, 4351 },
4412 4352
4413 /** 4353 /**
4414 * @type {!Polymer.IronMeta} 4354 * @type {!Polymer.IronMeta}
4415 */ 4355 */
4416 _meta: { 4356 _meta: {
4417 value: Polymer.Base.create('iron-meta', {type: 'iconset'}), 4357 value: Polymer.Base.create('iron-meta', {type: 'iconset'})
4418 observer: '_updateIcon'
4419 } 4358 }
4420 4359
4421 }, 4360 },
4422 4361
4423 _DEFAULT_ICONSET: 'icons', 4362 _DEFAULT_ICONSET: 'icons',
4424 4363
4425 _iconChanged: function(icon) { 4364 _iconChanged: function(icon) {
4426 var parts = (icon || '').split(':'); 4365 var parts = (icon || '').split(':');
4427 this._iconName = parts.pop(); 4366 this._iconName = parts.pop();
4428 this._iconsetName = parts.pop() || this._DEFAULT_ICONSET; 4367 this._iconsetName = parts.pop() || this._DEFAULT_ICONSET;
4429 this._updateIcon(); 4368 this._updateIcon();
4430 }, 4369 },
4431 4370
4432 _srcChanged: function(src) { 4371 _srcChanged: function(src) {
4433 this._updateIcon(); 4372 this._updateIcon();
4434 }, 4373 },
4435 4374
4436 _usesIconset: function() { 4375 _usesIconset: function() {
4437 return this.icon || !this.src; 4376 return this.icon || !this.src;
4438 }, 4377 },
4439 4378
4440 /** @suppress {visibility} */ 4379 /** @suppress {visibility} */
4441 _updateIcon: function() { 4380 _updateIcon: function() {
4442 if (this._usesIconset()) { 4381 if (this._usesIconset()) {
4443 if (this._img && this._img.parentNode) { 4382 if (this._iconsetName) {
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) {
4451 this._iconset = /** @type {?Polymer.Iconset} */ ( 4383 this._iconset = /** @type {?Polymer.Iconset} */ (
4452 this._meta.byKey(this._iconsetName)); 4384 this._meta.byKey(this._iconsetName));
4453 if (this._iconset) { 4385 if (this._iconset) {
4454 this._iconset.applyIcon(this, this._iconName, this.theme); 4386 this._iconset.applyIcon(this, this._iconName, this.theme);
4455 this.unlisten(window, 'iron-iconset-added', '_updateIcon'); 4387 this.unlisten(window, 'iron-iconset-added', '_updateIcon');
4456 } else { 4388 } else {
4457 this.listen(window, 'iron-iconset-added', '_updateIcon'); 4389 this.listen(window, 'iron-iconset-added', '_updateIcon');
4458 } 4390 }
4459 } 4391 }
4460 } else { 4392 } else {
4461 if (this._iconset) {
4462 this._iconset.removeIcon(this);
4463 }
4464 if (!this._img) { 4393 if (!this._img) {
4465 this._img = document.createElement('img'); 4394 this._img = document.createElement('img');
4466 this._img.style.width = '100%'; 4395 this._img.style.width = '100%';
4467 this._img.style.height = '100%'; 4396 this._img.style.height = '100%';
4468 this._img.draggable = false; 4397 this._img.draggable = false;
4469 } 4398 }
4470 this._img.src = this.src; 4399 this._img.src = this.src;
4471 Polymer.dom(this.root).appendChild(this._img); 4400 Polymer.dom(this.root).appendChild(this._img);
4472 } 4401 }
4473 } 4402 }
(...skipping 248 matching lines...) Expand 10 before | Expand all | Expand 10 after
4722 } 4651 }
4723 }, 4652 },
4724 4653
4725 _disabledChanged: function(disabled, old) { 4654 _disabledChanged: function(disabled, old) {
4726 this.setAttribute('aria-disabled', disabled ? 'true' : 'false'); 4655 this.setAttribute('aria-disabled', disabled ? 'true' : 'false');
4727 this.style.pointerEvents = disabled ? 'none' : ''; 4656 this.style.pointerEvents = disabled ? 'none' : '';
4728 if (disabled) { 4657 if (disabled) {
4729 this._oldTabIndex = this.tabIndex; 4658 this._oldTabIndex = this.tabIndex;
4730 this.focused = false; 4659 this.focused = false;
4731 this.tabIndex = -1; 4660 this.tabIndex = -1;
4732 this.blur();
4733 } else if (this._oldTabIndex !== undefined) { 4661 } else if (this._oldTabIndex !== undefined) {
4734 this.tabIndex = this._oldTabIndex; 4662 this.tabIndex = this._oldTabIndex;
4735 } 4663 }
4736 }, 4664 },
4737 4665
4738 _changedControlState: function() { 4666 _changedControlState: function() {
4739 // _controlStateChanged is abstract, follow-on behaviors may implement it 4667 // _controlStateChanged is abstract, follow-on behaviors may implement it
4740 if (this._controlStateChanged) { 4668 if (this._controlStateChanged) {
4741 this._controlStateChanged(); 4669 this._controlStateChanged();
4742 } 4670 }
(...skipping 1710 matching lines...) Expand 10 before | Expand all | Expand 10 after
6453 6381
6454 /** 6382 /**
6455 * Sets the selection state for a given item to either selected or deselecte d. 6383 * Sets the selection state for a given item to either selected or deselecte d.
6456 * 6384 *
6457 * @method setItemSelected 6385 * @method setItemSelected
6458 * @param {*} item The item to select. 6386 * @param {*} item The item to select.
6459 * @param {boolean} isSelected True for selected, false for deselected. 6387 * @param {boolean} isSelected True for selected, false for deselected.
6460 */ 6388 */
6461 setItemSelected: function(item, isSelected) { 6389 setItemSelected: function(item, isSelected) {
6462 if (item != null) { 6390 if (item != null) {
6463 if (isSelected !== this.isSelected(item)) { 6391 if (isSelected) {
6464 // proceed to update selection only if requested state differs from cu rrent 6392 this.selection.push(item);
6465 if (isSelected) { 6393 } else {
6466 this.selection.push(item); 6394 var i = this.selection.indexOf(item);
6467 } else { 6395 if (i >= 0) {
6468 var i = this.selection.indexOf(item); 6396 this.selection.splice(i, 1);
6469 if (i >= 0) {
6470 this.selection.splice(i, 1);
6471 }
6472 } 6397 }
6473 if (this.selectCallback) { 6398 }
6474 this.selectCallback(item, isSelected); 6399 if (this.selectCallback) {
6475 } 6400 this.selectCallback(item, isSelected);
6476 } 6401 }
6477 } 6402 }
6478 }, 6403 },
6479 6404
6480 /** 6405 /**
6481 * Sets the selection state for a given item. If the `multi` property 6406 * Sets the selection state for a given item. If the `multi` property
6482 * is true, then the selected state of `item` will be toggled; otherwise 6407 * is true, then the selected state of `item` will be toggled; otherwise
6483 * the `item` will be selected. 6408 * the `item` will be selected.
6484 * 6409 *
6485 * @method select 6410 * @method select
(...skipping 136 matching lines...) Expand 10 before | Expand all | Expand 10 after
6622 type: Object, 6547 type: Object,
6623 value: function() { 6548 value: function() {
6624 return { 6549 return {
6625 'template': 1 6550 'template': 1
6626 }; 6551 };
6627 } 6552 }
6628 } 6553 }
6629 }, 6554 },
6630 6555
6631 observers: [ 6556 observers: [
6632 '_updateAttrForSelected(attrForSelected)', 6557 '_updateSelected(attrForSelected, selected)'
6633 '_updateSelected(selected)'
6634 ], 6558 ],
6635 6559
6636 created: function() { 6560 created: function() {
6637 this._bindFilterItem = this._filterItem.bind(this); 6561 this._bindFilterItem = this._filterItem.bind(this);
6638 this._selection = new Polymer.IronSelection(this._applySelection.bind(this )); 6562 this._selection = new Polymer.IronSelection(this._applySelection.bind(this ));
6639 }, 6563 },
6640 6564
6641 attached: function() { 6565 attached: function() {
6642 this._observer = this._observeItems(this); 6566 this._observer = this._observeItems(this);
6643 this._updateItems(); 6567 this._updateItems();
6644 if (!this._shouldUpdateSelection) { 6568 if (!this._shouldUpdateSelection) {
6645 this._updateSelected(); 6569 this._updateSelected(this.attrForSelected,this.selected)
6646 } 6570 }
6647 this._addListener(this.activateEvent); 6571 this._addListener(this.activateEvent);
6648 }, 6572 },
6649 6573
6650 detached: function() { 6574 detached: function() {
6651 if (this._observer) { 6575 if (this._observer) {
6652 Polymer.dom(this).unobserveNodes(this._observer); 6576 Polymer.dom(this).unobserveNodes(this._observer);
6653 } 6577 }
6654 this._removeListener(this.activateEvent); 6578 this._removeListener(this.activateEvent);
6655 }, 6579 },
(...skipping 72 matching lines...) Expand 10 before | Expand all | Expand 10 after
6728 this._removeListener(old); 6652 this._removeListener(old);
6729 this._addListener(eventName); 6653 this._addListener(eventName);
6730 }, 6654 },
6731 6655
6732 _updateItems: function() { 6656 _updateItems: function() {
6733 var nodes = Polymer.dom(this).queryDistributedElements(this.selectable || '*'); 6657 var nodes = Polymer.dom(this).queryDistributedElements(this.selectable || '*');
6734 nodes = Array.prototype.filter.call(nodes, this._bindFilterItem); 6658 nodes = Array.prototype.filter.call(nodes, this._bindFilterItem);
6735 this._setItems(nodes); 6659 this._setItems(nodes);
6736 }, 6660 },
6737 6661
6738 _updateAttrForSelected: function() {
6739 if (this._shouldUpdateSelection) {
6740 this.selected = this._indexToValue(this.indexOf(this.selectedItem));
6741 }
6742 },
6743
6744 _updateSelected: function() { 6662 _updateSelected: function() {
6745 this._selectSelected(this.selected); 6663 this._selectSelected(this.selected);
6746 }, 6664 },
6747 6665
6748 _selectSelected: function(selected) { 6666 _selectSelected: function(selected) {
6749 this._selection.select(this._valueToItem(this.selected)); 6667 this._selection.select(this._valueToItem(this.selected));
6750 }, 6668 },
6751 6669
6752 _filterItem: function(node) { 6670 _filterItem: function(node) {
6753 return !this._excludedLocalNames[node.localName]; 6671 return !this._excludedLocalNames[node.localName];
(...skipping 49 matching lines...) Expand 10 before | Expand all | Expand 10 after
6803 // observe items change under the given node. 6721 // observe items change under the given node.
6804 _observeItems: function(node) { 6722 _observeItems: function(node) {
6805 return Polymer.dom(node).observeNodes(function(mutations) { 6723 return Polymer.dom(node).observeNodes(function(mutations) {
6806 this._updateItems(); 6724 this._updateItems();
6807 6725
6808 if (this._shouldUpdateSelection) { 6726 if (this._shouldUpdateSelection) {
6809 this._updateSelected(); 6727 this._updateSelected();
6810 } 6728 }
6811 6729
6812 // Let other interested parties know about the change so that 6730 // Let other interested parties know about the change so that
6813 // we don't have to recreate mutation observers everywhere. 6731 // we don't have to recreate mutation observers everywher.
6814 this.fire('iron-items-changed', mutations, { 6732 this.fire('iron-items-changed', mutations, {
6815 bubbles: false, 6733 bubbles: false,
6816 cancelable: false 6734 cancelable: false
6817 }); 6735 });
6818 }); 6736 });
6819 }, 6737 },
6820 6738
6821 _activateHandler: function(e) { 6739 _activateHandler: function(e) {
6822 var t = e.target; 6740 var t = e.target;
6823 var items = this.items; 6741 var items = this.items;
(...skipping 43 matching lines...) Expand 10 before | Expand all | Expand 10 after
6867 */ 6785 */
6868 selectedItems: { 6786 selectedItems: {
6869 type: Array, 6787 type: Array,
6870 readOnly: true, 6788 readOnly: true,
6871 notify: true 6789 notify: true
6872 }, 6790 },
6873 6791
6874 }, 6792 },
6875 6793
6876 observers: [ 6794 observers: [
6877 '_updateSelected(selectedValues)' 6795 '_updateSelected(attrForSelected, selectedValues)'
6878 ], 6796 ],
6879 6797
6880 /** 6798 /**
6881 * Selects the given value. If the `multi` property is true, then the select ed state of the 6799 * Selects the given value. If the `multi` property is true, then the select ed state of the
6882 * `value` will be toggled; otherwise the `value` will be selected. 6800 * `value` will be toggled; otherwise the `value` will be selected.
6883 * 6801 *
6884 * @method select 6802 * @method select
6885 * @param {string|number} value the value to select. 6803 * @param {string|number} value the value to select.
6886 */ 6804 */
6887 select: function(value) { 6805 select: function(value) {
(...skipping 10 matching lines...) Expand all
6898 6816
6899 multiChanged: function(multi) { 6817 multiChanged: function(multi) {
6900 this._selection.multi = multi; 6818 this._selection.multi = multi;
6901 }, 6819 },
6902 6820
6903 get _shouldUpdateSelection() { 6821 get _shouldUpdateSelection() {
6904 return this.selected != null || 6822 return this.selected != null ||
6905 (this.selectedValues != null && this.selectedValues.length); 6823 (this.selectedValues != null && this.selectedValues.length);
6906 }, 6824 },
6907 6825
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
6920 _updateSelected: function() { 6826 _updateSelected: function() {
6921 if (this.multi) { 6827 if (this.multi) {
6922 this._selectMulti(this.selectedValues); 6828 this._selectMulti(this.selectedValues);
6923 } else { 6829 } else {
6924 this._selectSelected(this.selected); 6830 this._selectSelected(this.selected);
6925 } 6831 }
6926 }, 6832 },
6927 6833
6928 _selectMulti: function(values) { 6834 _selectMulti: function(values) {
6835 this._selection.clear();
6929 if (values) { 6836 if (values) {
6930 var selectedItems = this._valuesToItems(values); 6837 for (var i = 0; i < values.length; i++) {
6931 // clear all but the current selected items 6838 this._selection.setItemSelected(this._valueToItem(values[i]), true);
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);
6936 } 6839 }
6937 } else {
6938 this._selection.clear();
6939 } 6840 }
6940 }, 6841 },
6941 6842
6942 _selectionChange: function() { 6843 _selectionChange: function() {
6943 var s = this._selection.get(); 6844 var s = this._selection.get();
6944 if (this.multi) { 6845 if (this.multi) {
6945 this._setSelectedItems(s); 6846 this._setSelectedItems(s);
6946 } else { 6847 } else {
6947 this._setSelectedItems([s]); 6848 this._setSelectedItems([s]);
6948 this._setSelectedItem(s); 6849 this._setSelectedItem(s);
6949 } 6850 }
6950 }, 6851 },
6951 6852
6952 _toggleSelected: function(value) { 6853 _toggleSelected: function(value) {
6953 var i = this.selectedValues.indexOf(value); 6854 var i = this.selectedValues.indexOf(value);
6954 var unselected = i < 0; 6855 var unselected = i < 0;
6955 if (unselected) { 6856 if (unselected) {
6956 this.push('selectedValues',value); 6857 this.push('selectedValues',value);
6957 } else { 6858 } else {
6958 this.splice('selectedValues',i,1); 6859 this.splice('selectedValues',i,1);
6959 } 6860 }
6960 this._selection.setItemSelected(this._valueToItem(value), unselected); 6861 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);
6967 } 6862 }
6968 }; 6863 };
6969 6864
6970 /** @polymerBehavior */ 6865 /** @polymerBehavior */
6971 Polymer.IronMultiSelectableBehavior = [ 6866 Polymer.IronMultiSelectableBehavior = [
6972 Polymer.IronSelectableBehavior, 6867 Polymer.IronSelectableBehavior,
6973 Polymer.IronMultiSelectableBehaviorImpl 6868 Polymer.IronMultiSelectableBehaviorImpl
6974 ]; 6869 ];
6975 /** 6870 /**
6976 * `Polymer.IronMenuBehavior` implements accessible menu behavior. 6871 * `Polymer.IronMenuBehavior` implements accessible menu behavior.
(...skipping 213 matching lines...) Expand 10 before | Expand all | Expand 10 after
7190 * Handler that is called when the menu receives focus. 7085 * Handler that is called when the menu receives focus.
7191 * 7086 *
7192 * @param {FocusEvent} event A focus event. 7087 * @param {FocusEvent} event A focus event.
7193 */ 7088 */
7194 _onFocus: function(event) { 7089 _onFocus: function(event) {
7195 if (Polymer.IronMenuBehaviorImpl._shiftTabPressed) { 7090 if (Polymer.IronMenuBehaviorImpl._shiftTabPressed) {
7196 // do not focus the menu itself 7091 // do not focus the menu itself
7197 return; 7092 return;
7198 } 7093 }
7199 7094
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
7208 this.blur(); 7095 this.blur();
7209 7096
7210 // clear the cached focus item 7097 // clear the cached focus item
7211 this._defaultFocusAsync = this.async(function() { 7098 this._defaultFocusAsync = this.async(function() {
7212 // focus the selected item when the menu receives focus, or the first it em 7099 // focus the selected item when the menu receives focus, or the first it em
7213 // if no item is selected 7100 // if no item is selected
7214 var selectedItem = this.multi ? (this.selectedItems && this.selectedItem s[0]) : this.selectedItem; 7101 var selectedItem = this.multi ? (this.selectedItems && this.selectedItem s[0]) : this.selectedItem;
7215 7102
7216 this._setFocusedItem(null); 7103 this._setFocusedItem(null);
7217 7104
7218 if (selectedItem) { 7105 if (selectedItem) {
7219 this._setFocusedItem(selectedItem); 7106 this._setFocusedItem(selectedItem);
7220 } else { 7107 } else {
7221 this._setFocusedItem(this.items[0]); 7108 this._setFocusedItem(this.items[0]);
7222 } 7109 }
7223 // async 1ms to wait for `select` to get called from `_itemActivate` 7110 // async 1ms to wait for `select` to get called from `_itemActivate`
7224 }, 1); 7111 }, 1);
7225 }, 7112 },
7226 7113
7227 /** 7114 /**
7228 * Handler that is called when the up key is pressed. 7115 * Handler that is called when the up key is pressed.
7229 * 7116 *
7230 * @param {CustomEvent} event A key combination event. 7117 * @param {CustomEvent} event A key combination event.
7231 */ 7118 */
7232 _onUpKey: function(event) { 7119 _onUpKey: function(event) {
7233 // up and down arrows moves the focus 7120 // up and down arrows moves the focus
7234 this._focusPrevious(); 7121 this._focusPrevious();
7235 event.detail.keyboardEvent.preventDefault();
7236 }, 7122 },
7237 7123
7238 /** 7124 /**
7239 * Handler that is called when the down key is pressed. 7125 * Handler that is called when the down key is pressed.
7240 * 7126 *
7241 * @param {CustomEvent} event A key combination event. 7127 * @param {CustomEvent} event A key combination event.
7242 */ 7128 */
7243 _onDownKey: function(event) { 7129 _onDownKey: function(event) {
7244 this._focusNext(); 7130 this._focusNext();
7245 event.detail.keyboardEvent.preventDefault();
7246 }, 7131 },
7247 7132
7248 /** 7133 /**
7249 * Handler that is called when the esc key is pressed. 7134 * Handler that is called when the esc key is pressed.
7250 * 7135 *
7251 * @param {CustomEvent} event A key combination event. 7136 * @param {CustomEvent} event A key combination event.
7252 */ 7137 */
7253 _onEscKey: function(event) { 7138 _onEscKey: function(event) {
7254 // esc blurs the control 7139 // esc blurs the control
7255 this.focusedItem.blur(); 7140 this.focusedItem.blur();
(...skipping 188 matching lines...) Expand 10 before | Expand all | Expand 10 after
7444 left: parseInt(target.marginLeft, 10) || 0 7329 left: parseInt(target.marginLeft, 10) || 0
7445 } 7330 }
7446 }; 7331 };
7447 }, 7332 },
7448 7333
7449 /** 7334 /**
7450 * Resets the target element's position and size constraints, and clear 7335 * Resets the target element's position and size constraints, and clear
7451 * the memoized data. 7336 * the memoized data.
7452 */ 7337 */
7453 resetFit: function() { 7338 resetFit: function() {
7339 if (!this._fitInfo || !this._fitInfo.sizedBy.height) {
7340 this.sizingTarget.style.maxHeight = '';
7341 this.style.top = this._fitInfo ? this._fitInfo.inlineStyle.top : '';
7342 }
7454 if (!this._fitInfo || !this._fitInfo.sizedBy.width) { 7343 if (!this._fitInfo || !this._fitInfo.sizedBy.width) {
7455 this.sizingTarget.style.maxWidth = ''; 7344 this.sizingTarget.style.maxWidth = '';
7345 this.style.left = this._fitInfo ? this._fitInfo.inlineStyle.left : '';
7456 } 7346 }
7457 if (!this._fitInfo || !this._fitInfo.sizedBy.height) {
7458 this.sizingTarget.style.maxHeight = '';
7459 }
7460 this.style.top = this._fitInfo ? this._fitInfo.inlineStyle.top : '';
7461 this.style.left = this._fitInfo ? this._fitInfo.inlineStyle.left : '';
7462 if (this._fitInfo) { 7347 if (this._fitInfo) {
7463 this.style.position = this._fitInfo.positionedBy.css; 7348 this.style.position = this._fitInfo.positionedBy.css;
7464 } 7349 }
7465 this._fitInfo = null; 7350 this._fitInfo = null;
7466 }, 7351 },
7467 7352
7468 /** 7353 /**
7469 * Equivalent to calling `resetFit()` and `fit()`. Useful to call this after the element, 7354 * Equivalent to calling `resetFit()` and `fit()`. Useful to call this after the element,
7470 * the window, or the `fitInfo` element has been resized. 7355 * the window, or the `fitInfo` element has been resized.
7471 */ 7356 */
(...skipping 40 matching lines...) Expand 10 before | Expand all | Expand 10 after
7512 var offsetExtent = 'offset' + extent; 7397 var offsetExtent = 'offset' + extent;
7513 var sizingOffset = this[offsetExtent] - this.sizingTarget[offsetExtent]; 7398 var sizingOffset = this[offsetExtent] - this.sizingTarget[offsetExtent];
7514 this.sizingTarget.style['max' + extent] = (max - margin - offset - sizingO ffset) + 'px'; 7399 this.sizingTarget.style['max' + extent] = (max - margin - offset - sizingO ffset) + 'px';
7515 }, 7400 },
7516 7401
7517 /** 7402 /**
7518 * Centers horizontally and vertically if not already positioned. This also sets 7403 * Centers horizontally and vertically if not already positioned. This also sets
7519 * `position:fixed`. 7404 * `position:fixed`.
7520 */ 7405 */
7521 center: function() { 7406 center: function() {
7522 var positionedBy = this._fitInfo.positionedBy; 7407 if (!this._fitInfo.positionedBy.vertically || !this._fitInfo.positionedBy. horizontally) {
7523 if (positionedBy.vertically && positionedBy.horizontally) { 7408 // need position:fixed to center
7524 // Already positioned. 7409 this.style.position = 'fixed';
7525 return;
7526 } 7410 }
7527 // Need position:fixed to center 7411 if (!this._fitInfo.positionedBy.vertically) {
7528 this.style.position = 'fixed'; 7412 var top = (this._fitHeight - this.offsetHeight) / 2 + this._fitTop;
7529 // Take into account the offset caused by parents that create stacking 7413 top -= this._fitInfo.margin.top;
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;
7542 this.style.top = top + 'px'; 7414 this.style.top = top + 'px';
7543 } 7415 }
7544 if (!positionedBy.horizontally) { 7416 if (!this._fitInfo.positionedBy.horizontally) {
7545 var left = this._fitLeft - rect.left + (this._fitWidth - rect.width) / 2 ; 7417 var left = (this._fitWidth - this.offsetWidth) / 2 + this._fitLeft;
7418 left -= this._fitInfo.margin.left;
7546 this.style.left = left + 'px'; 7419 this.style.left = left + 'px';
7547 } 7420 }
7548 } 7421 }
7549 7422
7550 }; 7423 };
7551 /** 7424 /**
7552 * @struct 7425 * @struct
7553 * @constructor 7426 * @constructor
7554 */ 7427 */
7555 Polymer.IronOverlayManagerClass = function() { 7428 Polymer.IronOverlayManagerClass = function() {
7556 this._overlays = []; 7429 this._overlays = [];
7557 // Used to keep track of the last focused node before an overlay gets opened .
7558 this._lastFocusedNodes = [];
7559
7560 /** 7430 /**
7561 * iframes have a default z-index of 100, so this default should be at least 7431 * iframes have a default z-index of 100, so this default should be at least
7562 * that. 7432 * that.
7563 * @private {number} 7433 * @private {number}
7564 */ 7434 */
7565 this._minimumZ = 101; 7435 this._minimumZ = 101;
7566 7436
7567 this._backdrops = []; 7437 this._backdrops = [];
7568 7438 }
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 };
7614 7439
7615 Polymer.IronOverlayManagerClass.prototype._applyOverlayZ = function(overlay, a boveZ) { 7440 Polymer.IronOverlayManagerClass.prototype._applyOverlayZ = function(overlay, a boveZ) {
7616 this._setZ(overlay, aboveZ + 2); 7441 this._setZ(overlay, aboveZ + 2);
7617 }; 7442 };
7618 7443
7619 Polymer.IronOverlayManagerClass.prototype._setZ = function(element, z) { 7444 Polymer.IronOverlayManagerClass.prototype._setZ = function(element, z) {
7620 element.style.zIndex = z; 7445 element.style.zIndex = z;
7621 }; 7446 };
7622 7447
7623 /** 7448 /**
7624 * track overlays for z-index and focus managemant 7449 * track overlays for z-index and focus managemant
7625 */ 7450 */
7626 Polymer.IronOverlayManagerClass.prototype.addOverlay = function(overlay) { 7451 Polymer.IronOverlayManagerClass.prototype.addOverlay = function(overlay) {
7627 var minimumZ = Math.max(this.currentOverlayZ(), this._minimumZ); 7452 var minimumZ = Math.max(this.currentOverlayZ(), this._minimumZ);
7628 this._overlays.push(overlay); 7453 this._overlays.push(overlay);
7629 var newZ = this.currentOverlayZ(); 7454 var newZ = this.currentOverlayZ();
7630 if (newZ <= minimumZ) { 7455 if (newZ <= minimumZ) {
7631 this._applyOverlayZ(overlay, minimumZ); 7456 this._applyOverlayZ(overlay, minimumZ);
7632 } 7457 }
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);
7639 }; 7458 };
7640 7459
7641 Polymer.IronOverlayManagerClass.prototype.removeOverlay = function(overlay) { 7460 Polymer.IronOverlayManagerClass.prototype.removeOverlay = function(overlay) {
7642 var i = this._overlays.indexOf(overlay); 7461 var i = this._overlays.indexOf(overlay);
7643 if (i >= 0) { 7462 if (i >= 0) {
7644 this._overlays.splice(i, 1); 7463 this._overlays.splice(i, 1);
7645 this._setZ(overlay, ''); 7464 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);
7653 } 7465 }
7654 }; 7466 };
7655 7467
7656 Polymer.IronOverlayManagerClass.prototype.currentOverlay = function() { 7468 Polymer.IronOverlayManagerClass.prototype.currentOverlay = function() {
7657 var i = this._overlays.length - 1; 7469 var i = this._overlays.length - 1;
7658 while (this._overlays[i] && !this._overlays[i].opened) { 7470 while (this._overlays[i] && !this._overlays[i].opened) {
7659 --i; 7471 --i;
7660 } 7472 }
7661 return this._overlays[i]; 7473 return this._overlays[i];
7662 }; 7474 };
(...skipping 34 matching lines...) Expand 10 before | Expand all | Expand 10 after
7697 if (element.opened && element.withBackdrop) { 7509 if (element.opened && element.withBackdrop) {
7698 // no duplicates 7510 // no duplicates
7699 if (index === -1) { 7511 if (index === -1) {
7700 this._backdrops.push(element); 7512 this._backdrops.push(element);
7701 } 7513 }
7702 } else if (index >= 0) { 7514 } else if (index >= 0) {
7703 this._backdrops.splice(index, 1); 7515 this._backdrops.splice(index, 1);
7704 } 7516 }
7705 }; 7517 };
7706 7518
7519 Object.defineProperty(Polymer.IronOverlayManagerClass.prototype, "backdropElem ent", {
7520 get: function() {
7521 if (!this._backdropElement) {
7522 this._backdropElement = document.createElement('iron-overlay-backdrop');
7523 }
7524 return this._backdropElement;
7525 }
7526 });
7527
7707 Polymer.IronOverlayManagerClass.prototype.getBackdrops = function() { 7528 Polymer.IronOverlayManagerClass.prototype.getBackdrops = function() {
7708 return this._backdrops; 7529 return this._backdrops;
7709 }; 7530 };
7710 7531
7711 /** 7532 /**
7712 * Returns the z-index for the backdrop. 7533 * Returns the z-index for the backdrop.
7713 */ 7534 */
7714 Polymer.IronOverlayManagerClass.prototype.backdropZ = function() { 7535 Polymer.IronOverlayManagerClass.prototype.backdropZ = function() {
7715 return this._getOverlayZ(this._overlayWithBackdrop()) - 1; 7536 return this._getOverlayZ(this._overlayWithBackdrop()) - 1;
7716 }; 7537 };
(...skipping 209 matching lines...) Expand 10 before | Expand all | Expand 10 after
7926 7747
7927 /** 7748 /**
7928 * Returns the reason this dialog was last closed. 7749 * Returns the reason this dialog was last closed.
7929 */ 7750 */
7930 closingReason: { 7751 closingReason: {
7931 // was a getter before, but needs to be a property so other 7752 // was a getter before, but needs to be a property so other
7932 // behaviors can override this. 7753 // behaviors can override this.
7933 type: Object 7754 type: Object
7934 }, 7755 },
7935 7756
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
7953 _manager: { 7757 _manager: {
7954 type: Object, 7758 type: Object,
7955 value: Polymer.IronOverlayManager 7759 value: Polymer.IronOverlayManager
7956 }, 7760 },
7957 7761
7958 _boundOnCaptureClick: { 7762 _boundOnCaptureClick: {
7959 type: Function, 7763 type: Function,
7960 value: function() { 7764 value: function() {
7961 return this._onCaptureClick.bind(this); 7765 return this._onCaptureClick.bind(this);
7962 } 7766 }
7963 }, 7767 },
7964 7768
7769 _boundOnCaptureKeydown: {
7770 type: Function,
7771 value: function() {
7772 return this._onCaptureKeydown.bind(this);
7773 }
7774 },
7775
7965 _boundOnCaptureFocus: { 7776 _boundOnCaptureFocus: {
7966 type: Function, 7777 type: Function,
7967 value: function() { 7778 value: function() {
7968 return this._onCaptureFocus.bind(this); 7779 return this._onCaptureFocus.bind(this);
7969 } 7780 }
7970 }, 7781 },
7971 7782
7972 /** 7783 /** @type {?Node} */
7973 * The node being focused.
7974 * @type {?Node}
7975 */
7976 _focusedChild: { 7784 _focusedChild: {
7977 type: Object 7785 type: Object
7978 } 7786 }
7979 7787
7980 }, 7788 },
7981 7789
7982 keyBindings: {
7983 'esc': '__onEsc',
7984 'tab': '__onTab'
7985 },
7986
7987 listeners: { 7790 listeners: {
7988 'iron-resize': '_onIronResize' 7791 'iron-resize': '_onIronResize'
7989 }, 7792 },
7990 7793
7991 /** 7794 /**
7992 * The backdrop element. 7795 * The backdrop element.
7993 * @type {Node} 7796 * @type Node
7994 */ 7797 */
7995 get backdropElement() { 7798 get backdropElement() {
7996 return this._manager.backdropElement; 7799 return this._manager.backdropElement;
7997 }, 7800 },
7998 7801
7999 /**
8000 * Returns the node to give focus to.
8001 * @type {Node}
8002 */
8003 get _focusNode() { 7802 get _focusNode() {
8004 return this._focusedChild || Polymer.dom(this).querySelector('[autofocus]' ) || this; 7803 return this._focusedChild || Polymer.dom(this).querySelector('[autofocus]' ) || this;
8005 }, 7804 },
8006 7805
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
8059 ready: function() { 7806 ready: function() {
8060 // with-backdrop needs tabindex to be set in order to trap the focus. 7807 // with-backdrop need tabindex to be set in order to trap the focus.
8061 // If it is not set, IronOverlayBehavior will set it, and remove it if wit h-backdrop = false. 7808 // If it is not set, IronOverlayBehavior will set it, and remove it if wit h-backdrop = false.
8062 this.__shouldRemoveTabIndex = false; 7809 this.__shouldRemoveTabIndex = false;
8063 // Used for wrapping the focus on TAB / Shift+TAB.
8064 this.__firstFocusableNode = this.__lastFocusableNode = null;
8065 this._ensureSetup(); 7810 this._ensureSetup();
8066 }, 7811 },
8067 7812
8068 attached: function() { 7813 attached: function() {
8069 // Call _openedChanged here so that position can be computed correctly. 7814 // Call _openedChanged here so that position can be computed correctly.
8070 if (this.opened) { 7815 if (this._callOpenedWhenReady) {
8071 this._openedChanged(); 7816 this._openedChanged();
8072 } 7817 }
8073 this._observer = Polymer.dom(this).observeNodes(this._onNodesChange);
8074 }, 7818 },
8075 7819
8076 detached: function() { 7820 detached: function() {
8077 Polymer.dom(this).unobserveNodes(this._observer);
8078 this._observer = null;
8079 this.opened = false; 7821 this.opened = false;
8080 this._manager.trackBackdrop(this); 7822 this._manager.trackBackdrop(this);
8081 this._manager.removeOverlay(this); 7823 this._manager.removeOverlay(this);
8082 }, 7824 },
8083 7825
8084 /** 7826 /**
8085 * Toggle the opened state of the overlay. 7827 * Toggle the opened state of the overlay.
8086 */ 7828 */
8087 toggle: function() { 7829 toggle: function() {
8088 this._setCanceled(false); 7830 this._setCanceled(false);
(...skipping 11 matching lines...) Expand all
8100 /** 7842 /**
8101 * Close the overlay. 7843 * Close the overlay.
8102 */ 7844 */
8103 close: function() { 7845 close: function() {
8104 this._setCanceled(false); 7846 this._setCanceled(false);
8105 this.opened = false; 7847 this.opened = false;
8106 }, 7848 },
8107 7849
8108 /** 7850 /**
8109 * Cancels the overlay. 7851 * Cancels the overlay.
8110 * @param {?Event} event The original event
8111 */ 7852 */
8112 cancel: function(event) { 7853 cancel: function() {
8113 var cancelEvent = this.fire('iron-overlay-canceled', event, {cancelable: t rue}); 7854 var cancelEvent = this.fire('iron-overlay-canceled', undefined, {cancelabl e: true});
8114 if (cancelEvent.defaultPrevented) { 7855 if (cancelEvent.defaultPrevented) {
8115 return; 7856 return;
8116 } 7857 }
8117 7858
8118 this._setCanceled(true); 7859 this._setCanceled(true);
8119 this.opened = false; 7860 this.opened = false;
8120 }, 7861 },
8121 7862
8122 _ensureSetup: function() { 7863 _ensureSetup: function() {
8123 if (this._overlaySetup) { 7864 if (this._overlaySetup) {
8124 return; 7865 return;
8125 } 7866 }
8126 this._overlaySetup = true; 7867 this._overlaySetup = true;
8127 this.style.outline = 'none'; 7868 this.style.outline = 'none';
8128 this.style.display = 'none'; 7869 this.style.display = 'none';
8129 }, 7870 },
8130 7871
8131 _openedChanged: function() { 7872 _openedChanged: function() {
8132 if (this.opened) { 7873 if (this.opened) {
8133 this.removeAttribute('aria-hidden'); 7874 this.removeAttribute('aria-hidden');
8134 } else { 7875 } else {
8135 this.setAttribute('aria-hidden', 'true'); 7876 this.setAttribute('aria-hidden', 'true');
7877 Polymer.dom(this).unobserveNodes(this._observer);
8136 } 7878 }
8137 7879
8138 // wait to call after ready only if we're initially open 7880 // wait to call after ready only if we're initially open
8139 if (!this._overlaySetup) { 7881 if (!this._overlaySetup) {
7882 this._callOpenedWhenReady = this.opened;
8140 return; 7883 return;
8141 } 7884 }
8142 7885
8143 this._manager.trackBackdrop(this); 7886 this._manager.trackBackdrop(this);
8144 7887
8145 if (this.opened) { 7888 if (this.opened) {
8146 this._prepareRenderOpened(); 7889 this._prepareRenderOpened();
8147 } 7890 }
8148 7891
8149 if (this._openChangedAsync) { 7892 if (this._openChangedAsync) {
(...skipping 54 matching lines...) Expand 10 before | Expand all | Expand 10 after
8204 node.addEventListener(event, boundListener, capture); 7947 node.addEventListener(event, boundListener, capture);
8205 } else { 7948 } else {
8206 // disable document-wide tap recognizer 7949 // disable document-wide tap recognizer
8207 if (event === 'tap') { 7950 if (event === 'tap') {
8208 Polymer.Gestures.remove(document, 'tap', null); 7951 Polymer.Gestures.remove(document, 'tap', null);
8209 } 7952 }
8210 node.removeEventListener(event, boundListener, capture); 7953 node.removeEventListener(event, boundListener, capture);
8211 } 7954 }
8212 }, 7955 },
8213 7956
8214 _toggleListeners: function() { 7957 _toggleListeners: function () {
8215 this._toggleListener(this.opened, document, 'tap', this._boundOnCaptureCli ck, true); 7958 this._toggleListener(this.opened, document, 'tap', this._boundOnCaptureCli ck, true);
7959 this._toggleListener(this.opened, document, 'keydown', this._boundOnCaptur eKeydown, true);
8216 this._toggleListener(this.opened, document, 'focus', this._boundOnCaptureF ocus, true); 7960 this._toggleListener(this.opened, document, 'focus', this._boundOnCaptureF ocus, true);
8217 }, 7961 },
8218 7962
8219 // tasks which must occur before opening; e.g. making the element visible 7963 // tasks which must occur before opening; e.g. making the element visible
8220 _prepareRenderOpened: function() { 7964 _prepareRenderOpened: function() {
8221
8222 this._manager.addOverlay(this); 7965 this._manager.addOverlay(this);
8223 7966
8224 // Needed to calculate the size of the overlay so that transitions on its size
8225 // will have the correct starting points.
8226 this._preparePositioning(); 7967 this._preparePositioning();
8227 this.fit(); 7968 this.fit();
8228 this._finishPositioning(); 7969 this._finishPositioning();
8229 7970
8230 if (this.withBackdrop) { 7971 if (this.withBackdrop) {
8231 this.backdropElement.prepare(); 7972 this.backdropElement.prepare();
8232 } 7973 }
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 }
8239 }, 7974 },
8240 7975
8241 // tasks which cause the overlay to actually open; typically play an 7976 // tasks which cause the overlay to actually open; typically play an
8242 // animation 7977 // animation
8243 _renderOpened: function() { 7978 _renderOpened: function() {
8244 if (this.withBackdrop) { 7979 if (this.withBackdrop) {
8245 this.backdropElement.open(); 7980 this.backdropElement.open();
8246 } 7981 }
8247 this._finishRenderOpened(); 7982 this._finishRenderOpened();
8248 }, 7983 },
8249 7984
8250 _renderClosed: function() { 7985 _renderClosed: function() {
8251 if (this.withBackdrop) { 7986 if (this.withBackdrop) {
8252 this.backdropElement.close(); 7987 this.backdropElement.close();
8253 } 7988 }
8254 this._finishRenderClosed(); 7989 this._finishRenderClosed();
8255 }, 7990 },
8256 7991
8257 _finishRenderOpened: function() { 7992 _finishRenderOpened: function() {
8258 // This ensures the overlay is visible before we set the focus 7993 // focus the child node with [autofocus]
8259 // (by calling _onIronResize -> refit).
8260 this.notifyResize();
8261 // Focus the child node with [autofocus]
8262 this._applyFocus(); 7994 this._applyFocus();
8263 7995
7996 this._observer = Polymer.dom(this).observeNodes(this.notifyResize);
8264 this.fire('iron-overlay-opened'); 7997 this.fire('iron-overlay-opened');
8265 }, 7998 },
8266 7999
8267 _finishRenderClosed: function() { 8000 _finishRenderClosed: function() {
8268 // Hide the overlay and remove the backdrop. 8001 // hide the overlay and remove the backdrop
8269 this.resetFit(); 8002 this.resetFit();
8270 this.style.display = 'none'; 8003 this.style.display = 'none';
8271 this._manager.removeOverlay(this); 8004 this._manager.removeOverlay(this);
8272 8005
8006 this._focusedChild = null;
8273 this._applyFocus(); 8007 this._applyFocus();
8008
8274 this.notifyResize(); 8009 this.notifyResize();
8275
8276 this.fire('iron-overlay-closed', this.closingReason); 8010 this.fire('iron-overlay-closed', this.closingReason);
8277 }, 8011 },
8278 8012
8279 _preparePositioning: function() { 8013 _preparePositioning: function() {
8280 this.style.transition = this.style.webkitTransition = 'none'; 8014 this.style.transition = this.style.webkitTransition = 'none';
8281 this.style.transform = this.style.webkitTransform = 'none'; 8015 this.style.transform = this.style.webkitTransform = 'none';
8282 this.style.display = ''; 8016 this.style.display = '';
8283 }, 8017 },
8284 8018
8285 _finishPositioning: function() { 8019 _finishPositioning: function() {
8286 this.style.display = 'none'; 8020 this.style.display = 'none';
8287 this.style.transform = this.style.webkitTransform = ''; 8021 this.style.transform = this.style.webkitTransform = '';
8288 // Force layout layout to avoid application of transform. 8022 // force layout to avoid application of transform
8289 // Set offsetWidth to itself so that compilers won't remove it. 8023 /** @suppress {suspiciousCode} */ this.offsetWidth;
8290 this.offsetWidth = this.offsetWidth;
8291 this.style.transition = this.style.webkitTransition = ''; 8024 this.style.transition = this.style.webkitTransition = '';
8292 }, 8025 },
8293 8026
8294 _applyFocus: function() { 8027 _applyFocus: function() {
8295 if (this.opened) { 8028 if (this.opened) {
8296 if (!this.noAutoFocus) { 8029 if (!this.noAutoFocus) {
8297 this._focusNode.focus(); 8030 this._focusNode.focus();
8298 } 8031 }
8299 } else { 8032 } else {
8300 this._focusNode.blur(); 8033 this._focusNode.blur();
8301 this._focusedChild = null;
8302 this._manager.focusOverlay(); 8034 this._manager.focusOverlay();
8303 } 8035 }
8304 }, 8036 },
8305 8037
8306 _onCaptureClick: function(event) { 8038 _onCaptureClick: function(event) {
8307 if (this._manager.currentOverlay() === this && 8039 if (this._manager.currentOverlay() === this &&
8308 Polymer.dom(event).path.indexOf(this) === -1) { 8040 Polymer.dom(event).path.indexOf(this) === -1) {
8309 if (this.noCancelOnOutsideClick) { 8041 if (this.noCancelOnOutsideClick) {
8310 this._applyFocus(); 8042 this._applyFocus();
8311 } else { 8043 } else {
8312 this.cancel(event); 8044 this.cancel();
8313 } 8045 }
8314 } 8046 }
8315 }, 8047 },
8316 8048
8049 _onCaptureKeydown: function(event) {
8050 var ESC = 27;
8051 if (this._manager.currentOverlay() === this &&
8052 !this.noCancelOnEscKey &&
8053 event.keyCode === ESC) {
8054 this.cancel();
8055 }
8056 },
8057
8317 _onCaptureFocus: function (event) { 8058 _onCaptureFocus: function (event) {
8318 if (this._manager.currentOverlay() === this && this.withBackdrop) { 8059 if (this._manager.currentOverlay() === this &&
8060 this.withBackdrop) {
8319 var path = Polymer.dom(event).path; 8061 var path = Polymer.dom(event).path;
8320 if (path.indexOf(this) === -1) { 8062 if (path.indexOf(this) === -1) {
8321 event.stopPropagation(); 8063 event.stopPropagation();
8322 this._applyFocus(); 8064 this._applyFocus();
8323 } else { 8065 } else {
8324 this._focusedChild = path[0]; 8066 this._focusedChild = path[0];
8325 } 8067 }
8326 } 8068 }
8327 }, 8069 },
8328 8070
8329 _onIronResize: function() { 8071 _onIronResize: function() {
8330 if (this.opened) { 8072 if (this.opened) {
8331 this.refit(); 8073 this.refit();
8332 } 8074 }
8333 }, 8075 }
8334 8076
8335 /** 8077 /**
8336 * @protected 8078 * Fired after the `iron-overlay` opens.
8337 * Will call notifyResize if overlay is opened. 8079 * @event iron-overlay-opened
8338 * Can be overridden in order to avoid multiple observers on the same node. 8080 */
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 8081
8350 __onEsc: function(event) { 8082 /**
8351 // Not opened or not on top, so return. 8083 * Fired when the `iron-overlay` is canceled, but before it is closed.
8352 if (this._manager.currentOverlay() !== this) { 8084 * Cancel the event to prevent the `iron-overlay` from closing.
8353 return; 8085 * @event iron-overlay-canceled
8354 } 8086 */
8355 if (!this.noCancelOnEscKey) {
8356 this.cancel(event);
8357 }
8358 },
8359 8087
8360 __onTab: function(event) { 8088 /**
8361 // Not opened or not on top, so return. 8089 * Fired after the `iron-overlay` closes.
8362 if (this._manager.currentOverlay() !== this) { 8090 * @event iron-overlay-closed
8363 return; 8091 * @param {{canceled: (boolean|undefined)}} set to the `closingReason` attribute
8364 } 8092 */
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 }
8375 }
8376 }; 8093 };
8377 8094
8378 /** @polymerBehavior */ 8095 /** @polymerBehavior */
8379 Polymer.IronOverlayBehavior = [Polymer.IronA11yKeysBehavior, Polymer.IronFitBe havior, Polymer.IronResizableBehavior, Polymer.IronOverlayBehaviorImpl]; 8096 Polymer.IronOverlayBehavior = [Polymer.IronFitBehavior, Polymer.IronResizableB ehavior, 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 */
8400 /** 8097 /**
8401 * Use `Polymer.NeonAnimationBehavior` to implement an animation. 8098 * Use `Polymer.NeonAnimationBehavior` to implement an animation.
8402 * @polymerBehavior 8099 * @polymerBehavior
8403 */ 8100 */
8404 Polymer.NeonAnimationBehavior = { 8101 Polymer.NeonAnimationBehavior = {
8405 8102
8406 properties: { 8103 properties: {
8407 8104
8408 /** 8105 /**
8409 * Defines the animation timing. 8106 * Defines the animation timing.
(...skipping 1530 matching lines...) Expand 10 before | Expand all | Expand 10 after
9940 You can use custom validators that implement `Polymer.IronValidatorBehavior` wit h `<iron-input>`. 9637 You can use custom validators that implement `Polymer.IronValidatorBehavior` wit h `<iron-input>`.
9941 9638
9942 <input is="iron-input" validator="my-custom-validator"> 9639 <input is="iron-input" validator="my-custom-validator">
9943 9640
9944 ### Stopping invalid input 9641 ### Stopping invalid input
9945 9642
9946 It may be desirable to only allow users to enter certain characters. You can use the 9643 It may be desirable to only allow users to enter certain characters. You can use the
9947 `prevent-invalid-input` and `allowed-pattern` attributes together to accomplish this. This feature 9644 `prevent-invalid-input` and `allowed-pattern` attributes together to accomplish this. This feature
9948 is separate from validation, and `allowed-pattern` does not affect how the input is validated. 9645 is separate from validation, and `allowed-pattern` does not affect how the input is validated.
9949 9646
9950 <!-- only allow characters that match [0-9] --> 9647 \x3c!-- only allow characters that match [0-9] --\x3e
9951 <input is="iron-input" prevent-invalid-input allowed-pattern="[0-9]"> 9648 <input is="iron-input" prevent-invalid-input allowed-pattern="[0-9]">
9952 9649
9953 @hero hero.svg 9650 @hero hero.svg
9954 @demo demo/index.html 9651 @demo demo/index.html
9955 */ 9652 */
9956 9653
9957 Polymer({ 9654 Polymer({
9958 9655
9959 is: 'iron-input', 9656 is: 'iron-input',
9960 9657
(...skipping 509 matching lines...) Expand 10 before | Expand all | Expand 10 after
10470 10167
10471 /** 10168 /**
10472 * Returns the value of the search field. 10169 * Returns the value of the search field.
10473 * @return {string} 10170 * @return {string}
10474 */ 10171 */
10475 getValue: function() { 10172 getValue: function() {
10476 var searchInput = this.getSearchInput_(); 10173 var searchInput = this.getSearchInput_();
10477 return searchInput ? searchInput.value : ''; 10174 return searchInput ? searchInput.value : '';
10478 }, 10175 },
10479 10176
10177 /**
10178 * Sets the value of the search field, if it exists.
10179 * @param {string} value
10180 */
10181 setValue: function(value) {
10182 var searchInput = this.getSearchInput_();
10183 if (searchInput)
10184 searchInput.value = value;
10185 },
10186
10480 /** @param {SearchFieldDelegate} delegate */ 10187 /** @param {SearchFieldDelegate} delegate */
10481 setDelegate: function(delegate) { 10188 setDelegate: function(delegate) {
10482 this.delegate_ = delegate; 10189 this.delegate_ = delegate;
10483 }, 10190 },
10484 10191
10192 /** @return {Promise<boolean>} */
10485 showAndFocus: function() { 10193 showAndFocus: function() {
10486 this.showingSearch_ = true; 10194 this.showingSearch_ = true;
10487 this.focus_(); 10195 return this.focus_();
10488 },
10489
10490 /** @private */
10491 focus_: function() {
10492 this.async(function() {
10493 if (!this.showingSearch_)
10494 return;
10495
10496 var searchInput = this.getSearchInput_();
10497 if (searchInput)
10498 searchInput.focus();
10499 });
10500 }, 10196 },
10501 10197
10502 /** 10198 /**
10199 * @return {Promise<boolean>}
10200 * @private
10201 */
10202 focus_: function() {
10203 return new Promise(function(resolve) {
10204 this.async(function() {
10205 if (this.showingSearch_) {
10206 var searchInput = this.getSearchInput_();
10207 if (searchInput)
10208 searchInput.focus();
10209 }
10210 resolve(this.showingSearch_);
10211 });
10212 }.bind(this));
10213 },
10214
10215 /**
10503 * @return {?Element} 10216 * @return {?Element}
10504 * @private 10217 * @private
10505 */ 10218 */
10506 getSearchInput_: function() { 10219 getSearchInput_: function() {
10507 return this.$$('#search-input'); 10220 return this.$$('#search-input');
10508 }, 10221 },
10509 10222
10510 /** @private */ 10223 /** @private */
10511 onSearchTermSearch_: function() { 10224 onSearchTermSearch_: function() {
10512 if (this.delegate_) 10225 if (this.delegate_)
(...skipping 303 matching lines...) Expand 10 before | Expand all | Expand 10 after
10816 Manager.removeItem = function(index) { 10529 Manager.removeItem = function(index) {
10817 Manager.get().removeItem_(index); 10530 Manager.get().removeItem_(index);
10818 }; 10531 };
10819 10532
10820 Manager.updateItem = function(index, data) { 10533 Manager.updateItem = function(index, data) {
10821 Manager.get().updateItem_(index, data); 10534 Manager.get().updateItem_(index, data);
10822 }; 10535 };
10823 10536
10824 return {Manager: Manager}; 10537 return {Manager: Manager};
10825 }); 10538 });
10539 // Copyright (c) 2012 The Chromium Authors. All rights reserved.
10540 // Use of this source code is governed by a BSD-style license that can be
10541 // found in the LICENSE file.
10542
10543 // <include src="../../../../ui/webui/resources/js/i18n_template_no_process.js">
10544
10545 i18nTemplate.process(document, loadTimeData);
10826 // Copyright 2015 The Chromium Authors. All rights reserved. 10546 // Copyright 2015 The Chromium Authors. All rights reserved.
10827 // Use of this source code is governed by a BSD-style license that can be 10547 // Use of this source code is governed by a BSD-style license that can be
10828 // found in the LICENSE file. 10548 // found in the LICENSE file.
10829 10549
10830 window.addEventListener('load', downloads.Manager.onLoad); 10550 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