 6c16e97d52
			
		
	
	6c16e97d52
	
	
	
		
			
			Previously, when keepOnScroll was set to true, and the header was not yet to the top, there was a miscalculation in the 'top' number, casuing the header to scroll up higher than its gr-fixed-panel parent. This occurred because calculations for '_topLast' became inaccurate, and compounded on one another. This change fixes the problem by utilizing the position of the gr-fixed-panel element itself to get a top offset, since it is reatively positioned, this can be computed. gr-fixed-panel tests were not being run, and they were already failing. The tests have been updated to relfect how they should behave. Change-Id: If0156cc7b009498ee63dddf0cef1b7e8d786b17c
		
			
				
	
	
		
			197 lines
		
	
	
		
			4.9 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
			
		
		
	
	
			197 lines
		
	
	
		
			4.9 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
| /**
 | |
|  * @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.
 | |
|  */
 | |
| (function() {
 | |
|   'use strict';
 | |
| 
 | |
|   Polymer({
 | |
|     is: 'gr-fixed-panel',
 | |
| 
 | |
|     properties: {
 | |
|       floatingDisabled: Boolean,
 | |
|       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,
 | |
|       },
 | |
|       _webComponentsReady: Boolean,
 | |
|     },
 | |
| 
 | |
|     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});
 | |
|     },
 | |
| 
 | |
|     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;
 | |
|     },
 | |
|   });
 | |
| })();
 |