UNPKG

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