| /** |
| * @license |
| * Copyright (C) 2017 The Android Open Source Project |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| import '../../../styles/shared-styles.js'; |
| import {GestureEventListeners} from '@polymer/polymer/lib/mixins/gesture-event-listeners.js'; |
| import {LegacyElementMixin} from '@polymer/polymer/lib/legacy/legacy-element-mixin.js'; |
| import {PolymerElement} from '@polymer/polymer/polymer-element.js'; |
| import {htmlTemplate} from './gr-fixed-panel_html.js'; |
| |
| /** @extends PolymerElement */ |
| class GrFixedPanel extends GestureEventListeners( |
| LegacyElementMixin( |
| PolymerElement)) { |
| static get template() { return htmlTemplate; } |
| |
| static get is() { return 'gr-fixed-panel'; } |
| |
| static get properties() { |
| return { |
| floatingDisabled: { |
| type: Boolean, |
| value: false, |
| }, |
| readyForMeasure: { |
| type: Boolean, |
| observer: '_readyForMeasureObserver', |
| }, |
| keepOnScroll: { |
| type: Boolean, |
| value: false, |
| }, |
| _isMeasured: { |
| type: Boolean, |
| value: false, |
| }, |
| |
| /** |
| * Initial offset from the top of the document, in pixels. |
| */ |
| _topInitial: Number, |
| |
| /** |
| * Current offset from the top of the window, in pixels. |
| */ |
| _topLast: Number, |
| |
| _headerHeight: Number, |
| _headerFloating: { |
| type: Boolean, |
| value: false, |
| }, |
| _observer: { |
| type: Object, |
| value: null, |
| }, |
| /** |
| * If place before any other content defines how much |
| * of the content below it is covered by this panel |
| */ |
| floatingHeight: { |
| type: Number, |
| value: 0, |
| notify: true, |
| }, |
| |
| _webComponentsReady: Boolean, |
| }; |
| } |
| |
| static get observers() { |
| return [ |
| '_updateFloatingHeight(floatingDisabled, _isMeasured, _headerHeight)', |
| ]; |
| } |
| |
| _updateFloatingHeight(floatingDisabled, isMeasured, headerHeight) { |
| if ([ |
| floatingDisabled, |
| isMeasured, |
| headerHeight, |
| ].some(arg => arg === undefined)) { |
| return; |
| } |
| this.floatingHeight = |
| (!floatingDisabled && isMeasured) ? headerHeight : 0; |
| } |
| |
| /** @override */ |
| attached() { |
| super.attached(); |
| if (this.floatingDisabled) { |
| return; |
| } |
| // Enable content measure unless blocked by param. |
| if (this.readyForMeasure !== false) { |
| this.readyForMeasure = true; |
| } |
| this.listen(window, 'resize', 'update'); |
| this.listen(window, 'scroll', '_updateOnScroll'); |
| this._observer = new MutationObserver(this.update.bind(this)); |
| this._observer.observe(this.$.header, {childList: true, subtree: true}); |
| } |
| |
| /** @override */ |
| detached() { |
| super.detached(); |
| this.unlisten(window, 'scroll', '_updateOnScroll'); |
| this.unlisten(window, 'resize', 'update'); |
| if (this._observer) { |
| this._observer.disconnect(); |
| } |
| } |
| |
| _readyForMeasureObserver(readyForMeasure) { |
| if (readyForMeasure) { |
| this.update(); |
| } |
| } |
| |
| _computeHeaderClass(headerFloating, topLast) { |
| const fixedAtTop = this.keepOnScroll && topLast === 0; |
| return [ |
| headerFloating ? 'floating' : '', |
| fixedAtTop ? 'fixedAtTop' : '', |
| ].join(' '); |
| } |
| |
| unfloat() { |
| if (this.floatingDisabled) { |
| return; |
| } |
| this.$.header.style.top = ''; |
| this._headerFloating = false; |
| this.updateStyles({'--header-height': ''}); |
| } |
| |
| update() { |
| this.debounce('update', () => { |
| this._updateDebounced(); |
| }, 100); |
| } |
| |
| _updateOnScroll() { |
| this.debounce('update', () => { |
| this._updateDebounced(); |
| }); |
| } |
| |
| _updateDebounced() { |
| if (this.floatingDisabled) { |
| return; |
| } |
| this._isMeasured = false; |
| this._maybeFloatHeader(); |
| this._reposition(); |
| } |
| |
| _getElementTop() { |
| return this.getBoundingClientRect().top; |
| } |
| |
| _reposition() { |
| if (!this._headerFloating) { |
| return; |
| } |
| const header = this.$.header; |
| // Since the outer element is relative positioned, can use its top |
| // to determine how to position the inner header element. |
| const elemTop = this._getElementTop(); |
| let newTop; |
| if (this.keepOnScroll && elemTop < 0) { |
| // Should stick to the top. |
| newTop = 0; |
| } else { |
| // Keep in line with the outer element. |
| newTop = elemTop; |
| } |
| // Initialize top style if it doesn't exist yet. |
| if (!header.style.top && this._topLast === newTop) { |
| header.style.top = newTop; |
| } |
| if (this._topLast !== newTop) { |
| if (newTop === undefined) { |
| header.style.top = ''; |
| } else { |
| header.style.top = newTop + 'px'; |
| } |
| this._topLast = newTop; |
| } |
| } |
| |
| _measure() { |
| if (this._isMeasured) { |
| return; // Already measured. |
| } |
| const rect = this.$.header.getBoundingClientRect(); |
| if (rect.height === 0 && rect.width === 0) { |
| return; // Not ready for measurement yet. |
| } |
| const top = document.body.scrollTop + rect.top; |
| this._topLast = top; |
| this._headerHeight = rect.height; |
| this._topInitial = |
| this.getBoundingClientRect().top + document.body.scrollTop; |
| this._isMeasured = true; |
| } |
| |
| _isFloatingNeeded() { |
| return this.keepOnScroll || |
| document.body.scrollWidth > document.body.clientWidth; |
| } |
| |
| _maybeFloatHeader() { |
| if (!this._isFloatingNeeded()) { |
| return; |
| } |
| this._measure(); |
| if (this._isMeasured) { |
| this._floatHeader(); |
| } |
| } |
| |
| _floatHeader() { |
| this.updateStyles({'--header-height': this._headerHeight + 'px'}); |
| this._headerFloating = true; |
| } |
| } |
| |
| customElements.define(GrFixedPanel.is, GrFixedPanel); |