blob: 08593aac69756d523d9a83a45bbf0da417f91de2 [file] [log] [blame]
Copybara botbe50d492023-11-30 00:16:42 +01001'use strict';
2
3var _fullThrottle = require('../utils/full-throttle');
4
5var _fullThrottle2 = _interopRequireDefault(_fullThrottle);
6
7var _jsonUtils = require('../utils/json-utils');
8
9var _constants = require('../utils/constants');
10
11function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
12
13(function () {
14 'use strict';
15
16 var MDL_LAYOUT_CONTENT = 'mdl-layout__content';
17 var IS_SCROLL_CLASS = 'mdlext-is-scroll';
18
19 /**
20 * @constructor
21 * @param {Element} element The element that will be upgraded.
22 */
23 var MaterialExtStickyHeader = function MaterialExtStickyHeader(element) {
24 // Stores the element.
25 this.header_ = element;
26
27 // Heder listens to scroll events from content
28 this.content_ = null;
29 this.lastScrollTop_ = 0;
30
31 // Default config
32 this.config_ = {
33 visibleAtScrollEnd: false
34 };
35
36 this.mutationObserver_ = null;
37
38 this.drawing_ = false;
39
40 // Initialize instance.
41 this.init();
42 };
43
44 window['MaterialExtStickyHeader'] = MaterialExtStickyHeader;
45
46 /**
47 * Update header width
48 * @private
49 */
50 MaterialExtStickyHeader.prototype.recalcWidth_ = function () {
51 this.header_.style.width = this.content_.clientWidth + 'px';
52 };
53
54 var throttleResize = (0, _fullThrottle2.default)(function (self) {
55 return self.recalcWidth_();
56 });
57
58 /**
59 * Adjust header width when window resizes or oreientation changes
60 * @param event
61 * @private
62 */
63 MaterialExtStickyHeader.prototype.resizeHandler_ = function () /* event */{
64 throttleResize(this);
65 };
66
67 /**
68 * Update header position
69 * @private
70 */
71 MaterialExtStickyHeader.prototype.reposition_ = function () {
72
73 var currentContentScrollTop = this.content_.scrollTop;
74 var scrollDiff = this.lastScrollTop_ - currentContentScrollTop;
75
76 if (currentContentScrollTop <= 0) {
77 // Scrolled to the top. Header sticks to the top
78 this.header_.style.top = '0';
79 this.header_.classList.remove(IS_SCROLL_CLASS);
80 } else if (scrollDiff > 0) {
81
82 if (scrollDiff >= this.header_.offsetHeight) {
83
84 // Scrolled up. Header slides in
85 var headerTop = parseInt(window.getComputedStyle(this.header_).getPropertyValue('top')) || 0;
86 if (headerTop != 0) {
87 this.header_.style.top = '0';
88 this.header_.classList.add(IS_SCROLL_CLASS);
89 }
90 this.lastScrollTop_ = currentContentScrollTop;
91 }
92 return;
93 } else if (scrollDiff < 0) {
94 // Scrolled down
95 this.header_.classList.add(IS_SCROLL_CLASS);
96 var _headerTop = parseInt(window.getComputedStyle(this.header_).getPropertyValue('top')) || 0;
97
98 if (this.content_.scrollHeight - this.content_.scrollTop <= this.content_.offsetHeight) {
99 // Bottom of content
100 if (_headerTop != 0) {
101 this.header_.style.top = this.config_.visibleAtScrollEnd ? '0' : '-' + this.header_.offsetHeight + 'px';
102 }
103 } else {
104 _headerTop += scrollDiff;
105 var offsetHeight = this.header_.offsetHeight;
106 this.header_.style.top = (Math.abs(_headerTop) > offsetHeight ? -offsetHeight : _headerTop) + 'px';
107 }
108 }
109
110 this.lastScrollTop_ = currentContentScrollTop;
111 };
112
113 var throttleScroll = (0, _fullThrottle2.default)(function (self) {
114 return self.reposition_();
115 });
116
117 /**
118 * Scroll header when content scrolls
119 * @param event
120 * @private
121 */
122 MaterialExtStickyHeader.prototype.scrollHandler_ = function () /* event */{
123 throttleScroll(this);
124 };
125
126 /**
127 * Init header position
128 * @private
129 */
130 MaterialExtStickyHeader.prototype.updatePosition_ = function () /* event */{
131 this.recalcWidth_();
132 this.reposition_();
133 };
134
135 /**
136 * Add mutation observer
137 * @private
138 */
139 MaterialExtStickyHeader.prototype.addMutationObserver_ = function () {
140 var _this = this;
141
142 // jsdom does not support MutationObserver - so this is not testable
143 /* istanbul ignore next */
144 this.mutationObserver_ = new MutationObserver(function () /*mutations*/{
145 // Adjust header width if content changes (e.g. in a SPA)
146 _this.updatePosition_();
147 });
148
149 this.mutationObserver_.observe(this.content_, {
150 attributes: false,
151 childList: true,
152 characterData: false,
153 subtree: true
154 });
155 };
156
157 /**
158 * Removes event listeners
159 * @private
160 */
161 MaterialExtStickyHeader.prototype.removeListeners_ = function () {
162
163 window.removeEventListener('resize', this.resizeHandler_);
164 window.removeEventListener('orientationchange', this.resizeHandler_);
165
166 if (this.content_) {
167 this.content_.removeEventListener('scroll', this.scrollHandler_);
168 }
169
170 if (this.mutationObserver_) {
171 this.mutationObserver_.disconnect();
172 this.mutationObserver_ = null;
173 }
174 };
175
176 /**
177 * Initialize component
178 */
179 MaterialExtStickyHeader.prototype.init = function () {
180
181 if (this.header_) {
182
183 this.removeListeners_();
184
185 if (this.header_.hasAttribute('data-config')) {
186 this.config_ = (0, _jsonUtils.jsonStringToObject)(this.header_.getAttribute('data-config'));
187 }
188
189 this.content_ = this.header_.parentNode.querySelector('.' + MDL_LAYOUT_CONTENT) || null;
190
191 if (this.content_) {
192 this.content_.style.paddingTop = this.header_.offsetHeight + 'px'; // Make room for sticky header
193 this.lastScrollTop_ = this.content_.scrollTop;
194
195 this.content_.addEventListener('scroll', this.scrollHandler_.bind(this));
196 window.addEventListener('resize', this.resizeHandler_.bind(this));
197 window.addEventListener('orientationchange', this.resizeHandler_.bind(this));
198
199 this.addMutationObserver_();
200 this.updatePosition_();
201
202 // Set upgraded flag
203 this.header_.classList.add(_constants.IS_UPGRADED);
204 }
205 }
206 };
207
208 /*
209 * Downgrade component
210 * E.g remove listeners and clean up resources
211 *
212 * Nothing to clean
213 *
214 MaterialExtStickyHeader.prototype.mdlDowngrade_ = function() {
215 'use strict';
216 console.log('***** MaterialExtStickyHeader.prototype.mdlDowngrade_');
217 };
218 */
219
220 // The component registers itself. It can assume componentHandler is available
221 // in the global scope.
222 /* eslint no-undef: 0 */
223 componentHandler.register({
224 constructor: MaterialExtStickyHeader,
225 classAsString: 'MaterialExtStickyHeader',
226 cssClass: 'mdlext-js-sticky-header'
227 });
228})(); /**
229 * @license
230 * Copyright 2016 Leif Olsen. All Rights Reserved.
231 *
232 * Licensed under the Apache License, Version 2.0 (the "License");
233 * you may not use this file except in compliance with the License.
234 * You may obtain a copy of the License at
235 *
236 * http://www.apache.org/licenses/LICENSE-2.0
237 *
238 * Unless required by applicable law or agreed to in writing, software
239 * distributed under the License is distributed on an "AS IS" BASIS,
240 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
241 * See the License for the specific language governing permissions and
242 * limitations under the License.
243 *
244 * This code is built with Google Material Design Lite,
245 * which is Licensed under the Apache License, Version 2.0
246 */
247
248/**
249 * A sticky header makes site navigation easily accessible anywhere on the page and saves content space at the same.
250 * The header should auto-hide, i.e. hiding the header automatically when a user starts scrolling down the page and
251 * bringing the header back when a user might need it: they reach the bottom of the page or start scrolling up.
252 */