* @fileoverview Behavior for scrollable containers with <iron-list>.
*
* Any containers with the 'scrollable' attribute set will have the following
* classes toggled appropriately: can-scroll, is-scrolled, scrolled-to-bottom.
* These classes are used to style the container div and list elements
* appropriately, see cr_shared_style.css.
*
* The associated HTML should look something like:
* <div id="container" scrollable>
* <iron-list items="[[items]]" scroll-target="container">
* <template>
* <my-element item="[[item]] tabindex$="[[tabIndex]]"></my-element>
* </template>
* </iron-list>
* </div>
*
* In order to get correct keyboard focus (tab) behavior within the list,
* any elements with tabbable sub-elements also need to set tabindex, e.g:
*
* <dom-module id="my-element>
* <template>
* ...
* <paper-icon-button toggles active="{{opened}}" tabindex$="[[tabindex]]">
* </template>
* </dom-module>
*
* NOTE: If 'container' is not fixed size, it is important to call
* updateScrollableContents() when [[items]] changes, otherwise the container
* will not be sized correctly.
*
* NOTE: This file is deprecated in favor of cr_scrollable_mixin.ts. Don't use
* it in new code.
*/
import {beforeNextRender, Polymer} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
import 'chrome://resources/polymer/v3_0/iron-list/iron-list.js';
export const CrScrollableBehavior = {
intervalId_: null,
ready() {
beforeNextRender(this, () => {
this.requestUpdateScroll();
const scrollableElements =
this.shadowRoot.querySelectorAll('[scrollable]');
for (let i = 0; i < scrollableElements.length; i++) {
scrollableElements[i].addEventListener(
'scroll', this.updateScrollEvent_.bind(this));
}
});
},
detached() {
if (this.intervalId_ !== null) {
clearInterval(this.intervalId_);
}
},
* Called any time the contents of a scrollable container may have changed.
* This ensures that the <iron-list> contents of dynamically sized
* containers are resized correctly.
*/
updateScrollableContents() {
if (this.intervalId_ !== null) {
return;
}
this.requestUpdateScroll();
const nodeList = this.shadowRoot.querySelectorAll('[scrollable] iron-list');
if (!nodeList.length) {
return;
}
let nodesToResize = Array.from(nodeList).map(node => ({
node: node,
lastScrollHeight: 0,
}));
this.intervalId_ = window.setInterval(() => {
const checkAgain = [];
nodesToResize.forEach(({node, lastScrollHeight}) => {
const scrollHeight = node.parentNode.scrollHeight;
if (scrollHeight !== lastScrollHeight) {
const ironList = (node);
ironList.notifyResize();
}
if (scrollHeight <= 1 &&
window.getComputedStyle(node.parentNode).display !== 'none') {
checkAgain.push({
node: node,
lastScrollHeight: scrollHeight,
});
}
});
if (checkAgain.length === 0) {
window.clearInterval(this.intervalId_);
this.intervalId_ = null;
} else {
nodesToResize = checkAgain;
}
}, 10);
},
* Setup the initial scrolling related classes for each scrollable container.
* Called from ready() and updateScrollableContents(). May also be called
* directly when the contents change (e.g. when not using iron-list).
*/
requestUpdateScroll() {
requestAnimationFrame(function() {
const scrollableElements =
this.shadowRoot.querySelectorAll('[scrollable]');
for (let i = 0; i < scrollableElements.length; i++) {
this.updateScroll_( (scrollableElements[i]));
}
}.bind(this));
},
saveScroll(list) {
list.savedScrollTops = list.savedScrollTops || [];
list.savedScrollTops.push(list.scrollTarget.scrollTop);
},
restoreScroll(list) {
this.async(function() {
const scrollTop = list.savedScrollTops.shift();
if (scrollTop !== 0) {
list.scroll(0, scrollTop);
}
});
},
* Event wrapper for updateScroll_.
* @param {!Event} event
* @private
*/
updateScrollEvent_(event) {
const scrollable = (event.target);
this.updateScroll_(scrollable);
},
* This gets called once initially and any time a scrollable container
* scrolls.
* @param {!HTMLElement} scrollable
* @private
*/
updateScroll_(scrollable) {
scrollable.classList.toggle(
'can-scroll', scrollable.clientHeight < scrollable.scrollHeight);
scrollable.classList.toggle('is-scrolled', scrollable.scrollTop > 0);
scrollable.classList.toggle(
'scrolled-to-bottom',
scrollable.scrollTop + scrollable.clientHeight >=
scrollable.scrollHeight);
},
};
export class CrScrollableBehaviorInterface {
updateScrollableContents() {}
requestUpdateScroll() {}
}