UNPKG

9.21 kBJavaScriptView Raw
1/*
2 * Copyright 2018 Palantir Technologies, Inc. All rights reserved.
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16import { __extends } from "tslib";
17import classNames from "classnames";
18import * as React from "react";
19import { Boundary, Classes, DISPLAYNAME_PREFIX } from "../../common";
20import { OVERFLOW_LIST_OBSERVE_PARENTS_CHANGED } from "../../common/errors";
21import { shallowCompareKeys } from "../../common/utils";
22import { ResizeSensor } from "../resize-sensor/resizeSensor";
23/**
24 * Overflow list component.
25 *
26 * @see https://blueprintjs.com/docs/#core/components/overflow-list
27 */
28var OverflowList = /** @class */ (function (_super) {
29 __extends(OverflowList, _super);
30 function OverflowList() {
31 var _this = _super !== null && _super.apply(this, arguments) || this;
32 _this.state = {
33 chopSize: _this.defaultChopSize(),
34 lastChopSize: null,
35 lastOverflowCount: 0,
36 overflow: [],
37 repartitioning: false,
38 visible: _this.props.items,
39 };
40 _this.spacer = null;
41 _this.resize = function () {
42 _this.repartition();
43 };
44 return _this;
45 }
46 OverflowList.ofType = function () {
47 return OverflowList;
48 };
49 OverflowList.prototype.componentDidMount = function () {
50 this.repartition();
51 };
52 OverflowList.prototype.shouldComponentUpdate = function (nextProps, nextState) {
53 // We want this component to always re-render, even when props haven't changed, so that
54 // changes in the renderers' behavior can be reflected.
55 // The following statement prevents re-rendering only in the case where the state changes
56 // identity (i.e. setState was called), but the state is still the same when
57 // shallow-compared to the previous state. Original context: https://github.com/palantir/blueprint/pull/3278.
58 // We also ensure that we re-render if the props DO change (which isn't necessarily accounted for by other logic).
59 return this.props !== nextProps || !(this.state !== nextState && shallowCompareKeys(this.state, nextState));
60 };
61 OverflowList.prototype.componentDidUpdate = function (prevProps, prevState) {
62 var _a, _b;
63 if (prevProps.observeParents !== this.props.observeParents) {
64 console.warn(OVERFLOW_LIST_OBSERVE_PARENTS_CHANGED);
65 }
66 if (prevProps.collapseFrom !== this.props.collapseFrom ||
67 prevProps.items !== this.props.items ||
68 prevProps.minVisibleItems !== this.props.minVisibleItems ||
69 prevProps.overflowRenderer !== this.props.overflowRenderer ||
70 prevProps.alwaysRenderOverflow !== this.props.alwaysRenderOverflow ||
71 prevProps.visibleItemRenderer !== this.props.visibleItemRenderer) {
72 // reset visible state if the above props change.
73 this.setState({
74 chopSize: this.defaultChopSize(),
75 lastChopSize: null,
76 lastOverflowCount: 0,
77 overflow: [],
78 repartitioning: true,
79 visible: this.props.items,
80 });
81 }
82 var _c = this.state, repartitioning = _c.repartitioning, overflow = _c.overflow, lastOverflowCount = _c.lastOverflowCount;
83 if (
84 // if a resize operation has just completed
85 repartitioning === false &&
86 prevState.repartitioning === true) {
87 // only invoke the callback if the UI has actually changed
88 if (overflow.length !== lastOverflowCount) {
89 (_b = (_a = this.props).onOverflow) === null || _b === void 0 ? void 0 : _b.call(_a, overflow.slice());
90 }
91 }
92 else if (!shallowCompareKeys(prevState, this.state)) {
93 this.repartition();
94 }
95 };
96 OverflowList.prototype.render = function () {
97 var _this = this;
98 var _a = this.props, className = _a.className, collapseFrom = _a.collapseFrom, observeParents = _a.observeParents, style = _a.style, _b = _a.tagName, tagName = _b === void 0 ? "div" : _b, visibleItemRenderer = _a.visibleItemRenderer;
99 var overflow = this.maybeRenderOverflow();
100 var list = React.createElement(tagName, {
101 className: classNames(Classes.OVERFLOW_LIST, className),
102 style: style,
103 }, collapseFrom === Boundary.START ? overflow : null, this.state.visible.map(visibleItemRenderer), collapseFrom === Boundary.END ? overflow : null, React.createElement("div", { className: Classes.OVERFLOW_LIST_SPACER, ref: function (ref) { return (_this.spacer = ref); } }));
104 return (React.createElement(ResizeSensor, { onResize: this.resize, observeParents: observeParents }, list));
105 };
106 OverflowList.prototype.maybeRenderOverflow = function () {
107 var overflow = this.state.overflow;
108 if (overflow.length === 0 && !this.props.alwaysRenderOverflow) {
109 return null;
110 }
111 return this.props.overflowRenderer(overflow.slice());
112 };
113 OverflowList.prototype.repartition = function () {
114 var _this = this;
115 var _a;
116 if (this.spacer == null) {
117 return;
118 }
119 // if lastChopSize was 1, then our binary search has exhausted.
120 var partitionExhausted = this.state.lastChopSize === 1;
121 var minVisible = (_a = this.props.minVisibleItems) !== null && _a !== void 0 ? _a : 0;
122 // spacer has flex-shrink and width 1px so if it's much smaller then we know to shrink
123 var shouldShrink = this.spacer.offsetWidth < 0.9 && this.state.visible.length > minVisible;
124 // we only check partitionExhausted for shouldGrow to ensure shrinking is the final operation.
125 var shouldGrow = (this.spacer.offsetWidth >= 1 || this.state.visible.length < minVisible) &&
126 this.state.overflow.length > 0 &&
127 !partitionExhausted;
128 if (shouldShrink || shouldGrow) {
129 this.setState(function (state) {
130 var visible;
131 var overflow;
132 if (_this.props.collapseFrom === Boundary.END) {
133 var result = shiftElements(state.visible, state.overflow, _this.state.chopSize * (shouldShrink ? 1 : -1));
134 visible = result[0];
135 overflow = result[1];
136 }
137 else {
138 var result = shiftElements(state.overflow, state.visible, _this.state.chopSize * (shouldShrink ? -1 : 1));
139 overflow = result[0];
140 visible = result[1];
141 }
142 return {
143 chopSize: halve(state.chopSize),
144 lastChopSize: state.chopSize,
145 // if we're starting a new partition cycle, record the last overflow count so we can track whether the UI changes after the new overflow is calculated
146 lastOverflowCount: _this.isFirstPartitionCycle(state.chopSize)
147 ? state.overflow.length
148 : state.lastOverflowCount,
149 overflow: overflow,
150 repartitioning: true,
151 visible: visible,
152 };
153 });
154 }
155 else {
156 // repartition complete!
157 this.setState({
158 chopSize: this.defaultChopSize(),
159 lastChopSize: null,
160 repartitioning: false,
161 });
162 }
163 };
164 OverflowList.prototype.defaultChopSize = function () {
165 return halve(this.props.items.length);
166 };
167 OverflowList.prototype.isFirstPartitionCycle = function (currentChopSize) {
168 return currentChopSize === this.defaultChopSize();
169 };
170 OverflowList.displayName = "".concat(DISPLAYNAME_PREFIX, ".OverflowList");
171 OverflowList.defaultProps = {
172 alwaysRenderOverflow: false,
173 collapseFrom: Boundary.START,
174 minVisibleItems: 0,
175 };
176 return OverflowList;
177}(React.Component));
178export { OverflowList };
179function halve(num) {
180 return Math.ceil(num / 2);
181}
182function shiftElements(leftArray, rightArray, num) {
183 // if num is positive then elements are shifted from left-to-right, if negative then right-to-left
184 var allElements = leftArray.concat(rightArray);
185 var newLeftLength = leftArray.length - num;
186 if (newLeftLength <= 0) {
187 return [[], allElements];
188 }
189 else if (newLeftLength >= allElements.length) {
190 return [allElements, []];
191 }
192 var sliceIndex = allElements.length - newLeftLength;
193 return [allElements.slice(0, -sliceIndex), allElements.slice(-sliceIndex)];
194}
195//# sourceMappingURL=overflowList.js.map
\No newline at end of file