UNPKG

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