UNPKG

20 kBJavaScriptView Raw
1"use strict";
2Object.defineProperty(exports, "__esModule", { value: true });
3exports.ScrollablePaneBase = void 0;
4var tslib_1 = require("tslib");
5var React = require("react");
6var Utilities_1 = require("../../Utilities");
7var ScrollablePane_types_1 = require("./ScrollablePane.types");
8var getClassNames = Utilities_1.classNamesFunction();
9var ScrollablePaneBase = /** @class */ (function (_super) {
10 tslib_1.__extends(ScrollablePaneBase, _super);
11 function ScrollablePaneBase(props) {
12 var _this = _super.call(this, props) || this;
13 _this._root = React.createRef();
14 _this._stickyAboveRef = React.createRef();
15 _this._stickyBelowRef = React.createRef();
16 _this._contentContainer = React.createRef();
17 _this.subscribe = function (handler) {
18 _this._subscribers.add(handler);
19 };
20 _this.unsubscribe = function (handler) {
21 _this._subscribers.delete(handler);
22 };
23 _this.addSticky = function (sticky) {
24 _this._stickies.add(sticky);
25 // If ScrollablePane is mounted, then sort sticky in correct place
26 if (_this.contentContainer) {
27 sticky.setDistanceFromTop(_this.contentContainer);
28 _this.sortSticky(sticky);
29 }
30 };
31 _this.removeSticky = function (sticky) {
32 _this._stickies.delete(sticky);
33 _this._removeStickyFromContainers(sticky);
34 _this.notifySubscribers();
35 };
36 _this.sortSticky = function (sticky, sortAgain) {
37 if (_this.stickyAbove && _this.stickyBelow) {
38 if (sortAgain) {
39 _this._removeStickyFromContainers(sticky);
40 }
41 if (sticky.canStickyTop && sticky.stickyContentTop) {
42 _this._addToStickyContainer(sticky, _this.stickyAbove, sticky.stickyContentTop);
43 }
44 if (sticky.canStickyBottom && sticky.stickyContentBottom) {
45 _this._addToStickyContainer(sticky, _this.stickyBelow, sticky.stickyContentBottom);
46 }
47 }
48 };
49 _this.updateStickyRefHeights = function () {
50 var stickyItems = _this._stickies;
51 var stickyTopHeight = 0;
52 var stickyBottomHeight = 0;
53 stickyItems.forEach(function (sticky) {
54 var _a = sticky.state, isStickyTop = _a.isStickyTop, isStickyBottom = _a.isStickyBottom;
55 if (sticky.nonStickyContent) {
56 if (isStickyTop) {
57 stickyTopHeight += sticky.nonStickyContent.offsetHeight;
58 }
59 if (isStickyBottom) {
60 stickyBottomHeight += sticky.nonStickyContent.offsetHeight;
61 }
62 _this._checkStickyStatus(sticky);
63 }
64 });
65 _this.setState({
66 stickyTopHeight: stickyTopHeight,
67 stickyBottomHeight: stickyBottomHeight,
68 });
69 };
70 _this.notifySubscribers = function () {
71 if (_this.contentContainer) {
72 _this._subscribers.forEach(function (handle) {
73 // this.stickyBelow is passed in for calculating distance to determine Sticky status
74 handle(_this.contentContainer, _this.stickyBelow);
75 });
76 }
77 };
78 _this.getScrollPosition = function () {
79 if (_this.contentContainer) {
80 return _this.contentContainer.scrollTop;
81 }
82 return 0;
83 };
84 _this.syncScrollSticky = function (sticky) {
85 if (sticky && _this.contentContainer) {
86 sticky.syncScroll(_this.contentContainer);
87 }
88 };
89 _this._getScrollablePaneContext = function () {
90 return {
91 scrollablePane: {
92 subscribe: _this.subscribe,
93 unsubscribe: _this.unsubscribe,
94 addSticky: _this.addSticky,
95 removeSticky: _this.removeSticky,
96 updateStickyRefHeights: _this.updateStickyRefHeights,
97 sortSticky: _this.sortSticky,
98 notifySubscribers: _this.notifySubscribers,
99 syncScrollSticky: _this.syncScrollSticky,
100 },
101 };
102 };
103 _this._addToStickyContainer = function (sticky, stickyContainer, stickyContentToAdd) {
104 // If there's no children, append child to list, otherwise, sort though array and append at correct position
105 if (!stickyContainer.children.length) {
106 stickyContainer.appendChild(stickyContentToAdd);
107 }
108 else {
109 // If stickyContentToAdd isn't a child element of target container, then append
110 if (!stickyContainer.contains(stickyContentToAdd)) {
111 var stickyChildrenElements_1 = [].slice.call(stickyContainer.children);
112 var stickyList_1 = [];
113 // Get stickies. Filter by canStickyTop/Bottom, then sort by distance from top, and then
114 // filter by elements that are in the stickyContainer already.
115 _this._stickies.forEach(function (stickyItem) {
116 if (stickyContainer === _this.stickyAbove && sticky.canStickyTop) {
117 stickyList_1.push(stickyItem);
118 }
119 else if (sticky.canStickyBottom) {
120 stickyList_1.push(stickyItem);
121 }
122 });
123 var stickyListSorted = stickyList_1
124 .sort(function (a, b) {
125 return (a.state.distanceFromTop || 0) - (b.state.distanceFromTop || 0);
126 })
127 .filter(function (item) {
128 var stickyContent = stickyContainer === _this.stickyAbove ? item.stickyContentTop : item.stickyContentBottom;
129 if (stickyContent) {
130 return stickyChildrenElements_1.indexOf(stickyContent) > -1;
131 }
132 return false;
133 });
134 // Get first element that has a distance from top that is further than our sticky that is being added
135 var targetStickyToAppendBefore = undefined;
136 for (var _i = 0, stickyListSorted_1 = stickyListSorted; _i < stickyListSorted_1.length; _i++) {
137 var stickyListItem = stickyListSorted_1[_i];
138 if ((stickyListItem.state.distanceFromTop || 0) >= (sticky.state.distanceFromTop || 0)) {
139 targetStickyToAppendBefore = stickyListItem;
140 break;
141 }
142 }
143 // If target element to append before is known, grab respective stickyContentTop/Bottom element
144 // and insert before
145 var targetContainer = null;
146 if (targetStickyToAppendBefore) {
147 targetContainer =
148 stickyContainer === _this.stickyAbove
149 ? targetStickyToAppendBefore.stickyContentTop
150 : targetStickyToAppendBefore.stickyContentBottom;
151 }
152 stickyContainer.insertBefore(stickyContentToAdd, targetContainer);
153 }
154 }
155 };
156 _this._removeStickyFromContainers = function (sticky) {
157 if (_this.stickyAbove && sticky.stickyContentTop && _this.stickyAbove.contains(sticky.stickyContentTop)) {
158 _this.stickyAbove.removeChild(sticky.stickyContentTop);
159 }
160 if (_this.stickyBelow && sticky.stickyContentBottom && _this.stickyBelow.contains(sticky.stickyContentBottom)) {
161 _this.stickyBelow.removeChild(sticky.stickyContentBottom);
162 }
163 };
164 _this._onWindowResize = function () {
165 var scrollbarWidth = _this._getScrollbarWidth();
166 var scrollbarHeight = _this._getScrollbarHeight();
167 _this.setState({
168 scrollbarWidth: scrollbarWidth,
169 scrollbarHeight: scrollbarHeight,
170 });
171 _this.notifySubscribers();
172 };
173 _this._getStickyContainerStyle = function (height, isTop) {
174 return tslib_1.__assign(tslib_1.__assign({ height: height }, (Utilities_1.getRTL(_this.props.theme)
175 ? {
176 right: '0',
177 left: (_this.state.scrollbarWidth || _this._getScrollbarWidth() || 0) + "px",
178 }
179 : {
180 left: '0',
181 right: (_this.state.scrollbarWidth || _this._getScrollbarWidth() || 0) + "px",
182 })), (isTop
183 ? {
184 top: '0',
185 }
186 : {
187 bottom: (_this.state.scrollbarHeight || _this._getScrollbarHeight() || 0) + "px",
188 }));
189 };
190 _this._onScroll = function () {
191 var contentContainer = _this.contentContainer;
192 if (contentContainer) {
193 _this._stickies.forEach(function (sticky) {
194 sticky.syncScroll(contentContainer);
195 });
196 }
197 _this._notifyThrottled();
198 };
199 _this._subscribers = new Set();
200 _this._stickies = new Set();
201 Utilities_1.initializeComponentRef(_this);
202 _this._async = new Utilities_1.Async(_this);
203 _this._events = new Utilities_1.EventGroup(_this);
204 _this.state = {
205 stickyTopHeight: 0,
206 stickyBottomHeight: 0,
207 scrollbarWidth: 0,
208 scrollbarHeight: 0,
209 };
210 _this._notifyThrottled = _this._async.throttle(_this.notifySubscribers, 50);
211 return _this;
212 }
213 Object.defineProperty(ScrollablePaneBase.prototype, "root", {
214 get: function () {
215 return this._root.current;
216 },
217 enumerable: false,
218 configurable: true
219 });
220 Object.defineProperty(ScrollablePaneBase.prototype, "stickyAbove", {
221 get: function () {
222 return this._stickyAboveRef.current;
223 },
224 enumerable: false,
225 configurable: true
226 });
227 Object.defineProperty(ScrollablePaneBase.prototype, "stickyBelow", {
228 get: function () {
229 return this._stickyBelowRef.current;
230 },
231 enumerable: false,
232 configurable: true
233 });
234 Object.defineProperty(ScrollablePaneBase.prototype, "contentContainer", {
235 get: function () {
236 return this._contentContainer.current;
237 },
238 enumerable: false,
239 configurable: true
240 });
241 ScrollablePaneBase.prototype.componentDidMount = function () {
242 var _this = this;
243 var initialScrollPosition = this.props.initialScrollPosition;
244 this._events.on(this.contentContainer, 'scroll', this._onScroll);
245 this._events.on(window, 'resize', this._onWindowResize);
246 if (this.contentContainer && initialScrollPosition) {
247 this.contentContainer.scrollTop = initialScrollPosition;
248 }
249 // Set sticky distances from top property, then sort in correct order and notify subscribers
250 this.setStickiesDistanceFromTop();
251 this._stickies.forEach(function (sticky) {
252 _this.sortSticky(sticky);
253 });
254 this.notifySubscribers();
255 if ('MutationObserver' in window) {
256 this._mutationObserver = new MutationObserver(function (mutation) {
257 // Function to check if mutation is occuring in stickyAbove or stickyBelow
258 function checkIfMutationIsSticky(mutationRecord) {
259 if (this.stickyAbove !== null && this.stickyBelow !== null) {
260 return this.stickyAbove.contains(mutationRecord.target) || this.stickyBelow.contains(mutationRecord.target);
261 }
262 return false;
263 }
264 // Compute the scrollbar height, which might have changed if the content's width changed and caused overflow
265 var scrollbarHeight = _this._getScrollbarHeight();
266 // If the scrollbar height changed, update state so it's postioned correctly below sticky footer
267 if (scrollbarHeight !== _this.state.scrollbarHeight) {
268 _this.setState({
269 scrollbarHeight: scrollbarHeight,
270 });
271 }
272 // Notify subscribers again to re-check whether Sticky should be Sticky'd or not
273 _this.notifySubscribers();
274 // If mutation occurs in sticky header or footer, then update sticky top/bottom heights
275 if (mutation.some(checkIfMutationIsSticky.bind(_this))) {
276 _this.updateStickyRefHeights();
277 }
278 else {
279 // If mutation occurs in scrollable region, then find Sticky it belongs to and force update
280 var stickyList_2 = [];
281 _this._stickies.forEach(function (sticky) {
282 if (sticky.root && sticky.root.contains(mutation[0].target)) {
283 stickyList_2.push(sticky);
284 }
285 });
286 if (stickyList_2.length) {
287 stickyList_2.forEach(function (sticky) {
288 sticky.forceUpdate();
289 });
290 }
291 }
292 });
293 if (this.root) {
294 this._mutationObserver.observe(this.root, {
295 childList: true,
296 attributes: true,
297 subtree: true,
298 characterData: true,
299 });
300 }
301 }
302 };
303 ScrollablePaneBase.prototype.componentWillUnmount = function () {
304 this._events.dispose();
305 this._async.dispose();
306 if (this._mutationObserver) {
307 this._mutationObserver.disconnect();
308 }
309 };
310 // Only updates if props/state change, just to prevent excessive setState with updateStickyRefHeights
311 ScrollablePaneBase.prototype.shouldComponentUpdate = function (nextProps, nextState) {
312 return (this.props.children !== nextProps.children ||
313 this.props.initialScrollPosition !== nextProps.initialScrollPosition ||
314 this.props.className !== nextProps.className ||
315 this.state.stickyTopHeight !== nextState.stickyTopHeight ||
316 this.state.stickyBottomHeight !== nextState.stickyBottomHeight ||
317 this.state.scrollbarWidth !== nextState.scrollbarWidth ||
318 this.state.scrollbarHeight !== nextState.scrollbarHeight);
319 };
320 ScrollablePaneBase.prototype.componentDidUpdate = function (prevProps, prevState) {
321 var initialScrollPosition = this.props.initialScrollPosition;
322 if (this.contentContainer &&
323 typeof initialScrollPosition === 'number' &&
324 prevProps.initialScrollPosition !== initialScrollPosition) {
325 this.contentContainer.scrollTop = initialScrollPosition;
326 }
327 // Update subscribers when stickyTopHeight/stickyBottomHeight changes
328 if (prevState.stickyTopHeight !== this.state.stickyTopHeight ||
329 prevState.stickyBottomHeight !== this.state.stickyBottomHeight) {
330 this.notifySubscribers();
331 }
332 this._async.setTimeout(this._onWindowResize, 0);
333 };
334 ScrollablePaneBase.prototype.render = function () {
335 var _a = this.props, className = _a.className, scrollContainerFocus = _a.scrollContainerFocus, scrollContainerAriaLabel = _a.scrollContainerAriaLabel, theme = _a.theme, styles = _a.styles, onScroll = _a.onScroll;
336 var _b = this.state, stickyTopHeight = _b.stickyTopHeight, stickyBottomHeight = _b.stickyBottomHeight;
337 var classNames = getClassNames(styles, {
338 theme: theme,
339 className: className,
340 scrollbarVisibility: this.props.scrollbarVisibility,
341 });
342 var scrollContainerProps = scrollContainerFocus
343 ? {
344 role: 'group',
345 tabIndex: 0,
346 'aria-label': scrollContainerAriaLabel,
347 onScroll: onScroll,
348 }
349 : {
350 onScroll: onScroll,
351 };
352 return (React.createElement("div", tslib_1.__assign({}, Utilities_1.getNativeProps(tslib_1.__assign({}, this.props), Utilities_1.divProperties,
353 // on React 17 onScroll is not being invoked on root element,
354 // as a fix this method will be provided to the container element
355 ['onScroll']), { ref: this._root, className: classNames.root }),
356 React.createElement("div", { ref: this._stickyAboveRef, className: classNames.stickyAbove, style: this._getStickyContainerStyle(stickyTopHeight, true) }),
357 React.createElement("div", tslib_1.__assign({ ref: this._contentContainer }, scrollContainerProps, { className: classNames.contentContainer, "data-is-scrollable": true }),
358 React.createElement(ScrollablePane_types_1.ScrollablePaneContext.Provider, { value: this._getScrollablePaneContext() }, this.props.children)),
359 React.createElement("div", { className: classNames.stickyBelow, style: this._getStickyContainerStyle(stickyBottomHeight, false) },
360 React.createElement("div", { ref: this._stickyBelowRef, className: classNames.stickyBelowItems }))));
361 };
362 ScrollablePaneBase.prototype.setStickiesDistanceFromTop = function () {
363 var _this = this;
364 if (this.contentContainer) {
365 this._stickies.forEach(function (sticky) {
366 sticky.setDistanceFromTop(_this.contentContainer);
367 });
368 }
369 };
370 ScrollablePaneBase.prototype.forceLayoutUpdate = function () {
371 this._onWindowResize();
372 };
373 ScrollablePaneBase.prototype._checkStickyStatus = function (sticky) {
374 if (this.stickyAbove && this.stickyBelow && this.contentContainer && sticky.nonStickyContent) {
375 // If sticky is sticky, then append content to appropriate container
376 if (sticky.state.isStickyTop || sticky.state.isStickyBottom) {
377 if (sticky.state.isStickyTop &&
378 !this.stickyAbove.contains(sticky.nonStickyContent) &&
379 sticky.stickyContentTop) {
380 sticky.addSticky(sticky.stickyContentTop);
381 }
382 if (sticky.state.isStickyBottom &&
383 !this.stickyBelow.contains(sticky.nonStickyContent) &&
384 sticky.stickyContentBottom) {
385 sticky.addSticky(sticky.stickyContentBottom);
386 }
387 }
388 else if (!this.contentContainer.contains(sticky.nonStickyContent)) {
389 // Reset sticky if it's not sticky and not in the contentContainer element
390 sticky.resetSticky();
391 }
392 }
393 };
394 ScrollablePaneBase.prototype._getScrollbarWidth = function () {
395 var contentContainer = this.contentContainer;
396 return contentContainer ? contentContainer.offsetWidth - contentContainer.clientWidth : 0;
397 };
398 ScrollablePaneBase.prototype._getScrollbarHeight = function () {
399 var contentContainer = this.contentContainer;
400 return contentContainer ? contentContainer.offsetHeight - contentContainer.clientHeight : 0;
401 };
402 return ScrollablePaneBase;
403}(React.Component));
404exports.ScrollablePaneBase = ScrollablePaneBase;
405//# sourceMappingURL=ScrollablePane.base.js.map
\No newline at end of file