diff --git a/components-chromium/iron-list/iron-list.js b/components-chromium/iron-list/iron-list.js index 67ec9a735773d..ad8e118645b56 100644 --- a/components-chromium/iron-list/iron-list.js +++ b/components-chromium/iron-list/iron-list.js @@ -207,19 +207,6 @@ to previous and next items in the list: ``` -### Styling - -You can use the `--iron-list-items-container` mixin to style the container of -items: - -```css -iron-list { - --iron-list-items-container: { - margin: auto; - }; -} -``` - ### Resizing `iron-list` lays out the items when it receives a notification via the @@ -267,7 +254,6 @@ Polymer({ } #items { - @apply --iron-list-items-container; position: relative; } @@ -376,7 +362,15 @@ Polymer({ * there's some offset between the scrolling element and the list. For * example: a header is placed above the list. */ - scrollOffset: {type: Number, value: 0} + scrollOffset: {type: Number, value: 0}, + + /** + * If set to true, focus on an element will be preserved after rerender. + */ + preserveFocus: { + type: Boolean, + value: false + } }, observers: [ @@ -1066,11 +1060,53 @@ Polymer({ newGrid && this._updateGridMetrics(); }, + /** + * Finds and returns the focused element (both within self and children's + * Shadow DOM). + * @return {?HTMLElement} + */ + _getFocusedElement: function() { + function doSearch(node, query) { + let result = null; + let type = node.nodeType; + if (type == Node.ELEMENT_NODE || type == Node.DOCUMENT_FRAGMENT_NODE) + result = node.querySelector(query); + if (result) + return result; + + let child = node.firstChild; + while (child !== null && result === null) { + result = doSearch(child, query); + child = child.nextSibling; + } + if (result) + return result; + + const shadowRoot = node.shadowRoot; + return shadowRoot ? doSearch(shadowRoot, query) : null; + } + + // Find out if any of the items are focused first, and only search + // recursively in the item that contains focus, to avoid a slow + // search of the entire list. + const focusWithin = doSearch(this, ':focus-within'); + return focusWithin ? doSearch(focusWithin, ':focus') : null; + }, + /** * Called when the items have changed. That is, reassignments * to `items`, splices or updates to a single item. */ _itemsChanged: function(change) { + var rendering = /^items(\.splices){0,1}$/.test(change.path); + var lastFocusedIndex, focusedElement; + if (rendering && this.preserveFocus) { + lastFocusedIndex = this._focusedVirtualIndex; + focusedElement = this._getFocusedElement(); + } + + var preservingFocus = rendering && this.preserveFocus && focusedElement; + if (change.path === 'items') { this._virtualStart = 0; this._physicalTop = 0; @@ -1082,7 +1118,7 @@ Polymer({ this._physicalItems = this._physicalItems || []; this._physicalSizes = this._physicalSizes || []; this._physicalStart = 0; - if (this._scrollTop > this._scrollOffset) { + if (this._scrollTop > this._scrollOffset && !preservingFocus) { this._resetScrollPosition(0); } this._removeFocusedItem(); @@ -1114,6 +1150,17 @@ Polymer({ } else if (change.path !== 'items.length') { this._forwardItemPath(change.path, change.value); } + + // If the list was in focus when updated, preserve the focus on item. + if (preservingFocus) { + flush(); + focusedElement.blur(); // paper- elements breaks when focused twice. + this._focusPhysicalItem( + Math.min(this.items.length - 1, lastFocusedIndex)); + if (!this._isIndexVisible(this._focusedVirtualIndex)) { + this.scrollToIndex(this._focusedVirtualIndex); + } + } }, _forwardItemPath: function(path, value) { @@ -1851,13 +1898,7 @@ Polymer({ if (!targetModel) { return; } - if (focusedModel === targetModel) { - // If the user focused the same item, then bring it into view if it's not - // visible. - if (!this._isIndexVisible(fidx)) { - this.scrollToIndex(fidx); - } - } else { + if (focusedModel !== targetModel) { this._restoreFocusedItem(); // Restore tabIndex for the currently focused item. if (focusedModel) {