UNPKG

16.6 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 { __assign, __decorate, __extends } from "tslib";
17import classNames from "classnames";
18import * as React from "react";
19import { polyfill } from "react-lifecycles-compat";
20import { AbstractPureComponent2, Classes, Intent } from "../../common";
21import * as Errors from "../../common/errors";
22import { DISPLAYNAME_PREFIX } from "../../common/props";
23import * as Utils from "../../common/utils";
24import { Handle } from "./handle";
25import { HandleInteractionKind, HandleType } from "./handleProps";
26import { argMin, fillValues, formatPercentage } from "./sliderUtils";
27/**
28 * SFC used to pass slider handle props to a `MultiSlider`.
29 * This element is not rendered directly.
30 */
31var MultiSliderHandle = function () { return null; };
32MultiSliderHandle.displayName = DISPLAYNAME_PREFIX + ".MultiSliderHandle";
33var MultiSlider = /** @class */ (function (_super) {
34 __extends(MultiSlider, _super);
35 function MultiSlider() {
36 var _this = _super !== null && _super.apply(this, arguments) || this;
37 _this.state = {
38 labelPrecision: getLabelPrecision(_this.props),
39 tickSize: 0,
40 tickSizeRatio: 0,
41 };
42 _this.handleElements = [];
43 _this.trackElement = null;
44 _this.addHandleRef = function (ref) {
45 if (ref != null) {
46 _this.handleElements.push(ref);
47 }
48 };
49 _this.maybeHandleTrackClick = function (event) {
50 if (_this.canHandleTrackEvent(event)) {
51 var foundHandle = _this.nearestHandleForValue(_this.handleElements, function (handle) {
52 return handle.mouseEventClientOffset(event);
53 });
54 if (foundHandle) {
55 foundHandle.beginHandleMovement(event);
56 }
57 }
58 };
59 _this.maybeHandleTrackTouch = function (event) {
60 if (_this.canHandleTrackEvent(event)) {
61 var foundHandle = _this.nearestHandleForValue(_this.handleElements, function (handle) {
62 return handle.touchEventClientOffset(event);
63 });
64 if (foundHandle) {
65 foundHandle.beginHandleTouchMovement(event);
66 }
67 }
68 };
69 _this.canHandleTrackEvent = function (event) {
70 var target = event.target;
71 // ensure event does not come from inside the handle
72 return !_this.props.disabled && target.closest("." + Classes.SLIDER_HANDLE) == null;
73 };
74 _this.getHandlerForIndex = function (index, callback) {
75 return function (newValue) {
76 callback === null || callback === void 0 ? void 0 : callback(_this.getNewHandleValues(newValue, index));
77 };
78 };
79 _this.handleChange = function (newValues) {
80 var _a, _b;
81 var handleProps = getSortedInteractiveHandleProps(_this.props);
82 var oldValues = handleProps.map(function (handle) { return handle.value; });
83 if (!Utils.arraysEqual(newValues, oldValues)) {
84 (_b = (_a = _this.props).onChange) === null || _b === void 0 ? void 0 : _b.call(_a, newValues);
85 handleProps.forEach(function (handle, index) {
86 var _a;
87 if (oldValues[index] !== newValues[index]) {
88 (_a = handle.onChange) === null || _a === void 0 ? void 0 : _a.call(handle, newValues[index]);
89 }
90 });
91 }
92 };
93 _this.handleRelease = function (newValues) {
94 var _a, _b;
95 var handleProps = getSortedInteractiveHandleProps(_this.props);
96 (_b = (_a = _this.props).onRelease) === null || _b === void 0 ? void 0 : _b.call(_a, newValues);
97 handleProps.forEach(function (handle, index) {
98 var _a;
99 (_a = handle.onRelease) === null || _a === void 0 ? void 0 : _a.call(handle, newValues[index]);
100 });
101 };
102 return _this;
103 }
104 MultiSlider_1 = MultiSlider;
105 MultiSlider.getDerivedStateFromProps = function (props) {
106 return { labelPrecision: MultiSlider_1.getLabelPrecision(props) };
107 };
108 MultiSlider.getLabelPrecision = function (_a) {
109 var labelPrecision = _a.labelPrecision, stepSize = _a.stepSize;
110 // infer default label precision from stepSize because that's how much the handle moves.
111 return labelPrecision == null ? Utils.countDecimalPlaces(stepSize) : labelPrecision;
112 };
113 MultiSlider.prototype.getSnapshotBeforeUpdate = function (prevProps) {
114 var prevHandleProps = getSortedInteractiveHandleProps(prevProps);
115 var newHandleProps = getSortedInteractiveHandleProps(this.props);
116 if (newHandleProps.length !== prevHandleProps.length) {
117 // clear refs
118 this.handleElements = [];
119 }
120 return null;
121 };
122 MultiSlider.prototype.render = function () {
123 var _a;
124 var _this = this;
125 var classes = classNames(Classes.SLIDER, (_a = {},
126 _a[Classes.DISABLED] = this.props.disabled,
127 _a[Classes.SLIDER + "-unlabeled"] = this.props.labelRenderer === false,
128 _a[Classes.VERTICAL] = this.props.vertical,
129 _a), this.props.className);
130 return (React.createElement("div", { className: classes, onMouseDown: this.maybeHandleTrackClick, onTouchStart: this.maybeHandleTrackTouch },
131 React.createElement("div", { className: Classes.SLIDER_TRACK, ref: function (ref) { return (_this.trackElement = ref); } }, this.renderTracks()),
132 React.createElement("div", { className: Classes.SLIDER_AXIS }, this.renderLabels()),
133 this.renderHandles()));
134 };
135 MultiSlider.prototype.componentDidMount = function () {
136 this.updateTickSize();
137 };
138 MultiSlider.prototype.componentDidUpdate = function (prevProps, prevState) {
139 _super.prototype.componentDidUpdate.call(this, prevProps, prevState);
140 this.updateTickSize();
141 };
142 MultiSlider.prototype.validateProps = function (props) {
143 if (props.stepSize <= 0) {
144 throw new Error(Errors.SLIDER_ZERO_STEP);
145 }
146 if (props.labelStepSize !== undefined && props.labelValues !== undefined) {
147 throw new Error(Errors.MULTISLIDER_WARN_LABEL_STEP_SIZE_LABEL_VALUES_MUTEX);
148 }
149 if (props.labelStepSize !== undefined && props.labelStepSize <= 0) {
150 throw new Error(Errors.SLIDER_ZERO_LABEL_STEP);
151 }
152 var anyInvalidChildren = false;
153 React.Children.forEach(props.children, function (child) {
154 // allow boolean coercion to omit nulls and false values
155 if (child && !Utils.isElementOfType(child, MultiSlider_1.Handle)) {
156 anyInvalidChildren = true;
157 }
158 });
159 if (anyInvalidChildren) {
160 throw new Error(Errors.MULTISLIDER_INVALID_CHILD);
161 }
162 };
163 MultiSlider.prototype.formatLabel = function (value, isHandleTooltip) {
164 if (isHandleTooltip === void 0) { isHandleTooltip = false; }
165 var labelRenderer = this.props.labelRenderer;
166 if (labelRenderer === false) {
167 return undefined;
168 }
169 else if (Utils.isFunction(labelRenderer)) {
170 return labelRenderer(value, { isHandleTooltip: isHandleTooltip });
171 }
172 else {
173 return value.toFixed(this.state.labelPrecision);
174 }
175 };
176 MultiSlider.prototype.renderLabels = function () {
177 var _this = this;
178 if (this.props.labelRenderer === false) {
179 return null;
180 }
181 var values = this.getLabelValues();
182 var _a = this.props, max = _a.max, min = _a.min;
183 var labels = values.map(function (step, i) {
184 var offsetPercentage = formatPercentage((step - min) / (max - min));
185 var style = _this.props.vertical ? { bottom: offsetPercentage } : { left: offsetPercentage };
186 return (React.createElement("div", { className: Classes.SLIDER_LABEL, key: i, style: style }, _this.formatLabel(step)));
187 });
188 return labels;
189 };
190 MultiSlider.prototype.renderTracks = function () {
191 var trackStops = getSortedHandleProps(this.props);
192 trackStops.push({ value: this.props.max });
193 // render from current to previous, then increment previous
194 var previous = { value: this.props.min };
195 var handles = [];
196 for (var index = 0; index < trackStops.length; index++) {
197 var current = trackStops[index];
198 handles.push(this.renderTrackFill(index, previous, current));
199 previous = current;
200 }
201 return handles;
202 };
203 MultiSlider.prototype.renderTrackFill = function (index, start, end) {
204 // ensure startRatio <= endRatio
205 var _a = [this.getOffsetRatio(start.value), this.getOffsetRatio(end.value)].sort(function (left, right) { return left - right; }), startRatio = _a[0], endRatio = _a[1];
206 var startOffset = formatPercentage(startRatio);
207 var endOffset = formatPercentage(1 - endRatio);
208 var orientationStyle = this.props.vertical
209 ? { bottom: startOffset, top: endOffset, left: 0 }
210 : { left: startOffset, right: endOffset, top: 0 };
211 var style = __assign(__assign({}, orientationStyle), (start.trackStyleAfter || end.trackStyleBefore || {}));
212 var classes = classNames(Classes.SLIDER_PROGRESS, Classes.intentClass(this.getTrackIntent(start, end)));
213 return React.createElement("div", { key: "track-" + index, className: classes, style: style });
214 };
215 MultiSlider.prototype.renderHandles = function () {
216 var _this = this;
217 var _a = this.props, disabled = _a.disabled, max = _a.max, min = _a.min, stepSize = _a.stepSize, vertical = _a.vertical;
218 var handleProps = getSortedInteractiveHandleProps(this.props);
219 if (handleProps.length === 0) {
220 return null;
221 }
222 return handleProps.map(function (_a, index) {
223 var _b;
224 var value = _a.value, type = _a.type, className = _a.className;
225 return (React.createElement(Handle, { className: classNames((_b = {},
226 _b[Classes.START] = type === HandleType.START,
227 _b[Classes.END] = type === HandleType.END,
228 _b), className), disabled: disabled, key: index + "-" + handleProps.length, label: _this.formatLabel(value, true), max: max, min: min, onChange: _this.getHandlerForIndex(index, _this.handleChange), onRelease: _this.getHandlerForIndex(index, _this.handleRelease), ref: _this.addHandleRef, stepSize: stepSize, tickSize: _this.state.tickSize, tickSizeRatio: _this.state.tickSizeRatio, value: value, vertical: vertical }));
229 });
230 };
231 MultiSlider.prototype.nearestHandleForValue = function (handles, getOffset) {
232 return argMin(handles, function (handle) {
233 var offset = getOffset(handle);
234 var offsetValue = handle.clientToValue(offset);
235 var handleValue = handle.props.value;
236 return Math.abs(offsetValue - handleValue);
237 });
238 };
239 MultiSlider.prototype.getNewHandleValues = function (newValue, oldIndex) {
240 var handleProps = getSortedInteractiveHandleProps(this.props);
241 var oldValues = handleProps.map(function (handle) { return handle.value; });
242 var newValues = oldValues.slice();
243 newValues[oldIndex] = newValue;
244 newValues.sort(function (left, right) { return left - right; });
245 var newIndex = newValues.indexOf(newValue);
246 var lockIndex = this.findFirstLockedHandleIndex(oldIndex, newIndex);
247 if (lockIndex === -1) {
248 fillValues(newValues, oldIndex, newIndex, newValue);
249 }
250 else {
251 // If pushing past a locked handle, discard the new value and only make the updates to clamp values against the lock.
252 var lockValue = oldValues[lockIndex];
253 fillValues(oldValues, oldIndex, lockIndex, lockValue);
254 return oldValues;
255 }
256 return newValues;
257 };
258 MultiSlider.prototype.findFirstLockedHandleIndex = function (startIndex, endIndex) {
259 var inc = startIndex < endIndex ? 1 : -1;
260 var handleProps = getSortedInteractiveHandleProps(this.props);
261 for (var index = startIndex + inc; index !== endIndex + inc; index += inc) {
262 if (handleProps[index].interactionKind !== HandleInteractionKind.PUSH) {
263 return index;
264 }
265 }
266 return -1;
267 };
268 MultiSlider.prototype.getLabelValues = function () {
269 var _a = this.props, labelStepSize = _a.labelStepSize, labelValues = _a.labelValues, min = _a.min, max = _a.max;
270 var values = [];
271 if (labelValues !== undefined) {
272 values = labelValues;
273 }
274 else {
275 for (var i = min; i < max || Utils.approxEqual(i, max); i += labelStepSize !== null && labelStepSize !== void 0 ? labelStepSize : 1) {
276 values.push(i);
277 }
278 }
279 return values;
280 };
281 MultiSlider.prototype.getOffsetRatio = function (value) {
282 return Utils.clamp((value - this.props.min) * this.state.tickSizeRatio, 0, 1);
283 };
284 MultiSlider.prototype.getTrackIntent = function (start, end) {
285 if (!this.props.showTrackFill) {
286 return Intent.NONE;
287 }
288 if (start.intentAfter !== undefined) {
289 return start.intentAfter;
290 }
291 else if (end !== undefined && end.intentBefore !== undefined) {
292 return end.intentBefore;
293 }
294 return this.props.defaultTrackIntent;
295 };
296 MultiSlider.prototype.updateTickSize = function () {
297 if (this.trackElement != null) {
298 var trackSize = this.props.vertical ? this.trackElement.clientHeight : this.trackElement.clientWidth;
299 var tickSizeRatio = 1 / (this.props.max - this.props.min);
300 var tickSize = trackSize * tickSizeRatio;
301 this.setState({ tickSize: tickSize, tickSizeRatio: tickSizeRatio });
302 }
303 };
304 var MultiSlider_1;
305 MultiSlider.defaultSliderProps = {
306 disabled: false,
307 max: 10,
308 min: 0,
309 showTrackFill: true,
310 stepSize: 1,
311 vertical: false,
312 };
313 MultiSlider.defaultProps = __assign(__assign({}, MultiSlider_1.defaultSliderProps), { defaultTrackIntent: Intent.NONE });
314 MultiSlider.displayName = DISPLAYNAME_PREFIX + ".MultiSlider";
315 MultiSlider.Handle = MultiSliderHandle;
316 MultiSlider = MultiSlider_1 = __decorate([
317 polyfill
318 ], MultiSlider);
319 return MultiSlider;
320}(AbstractPureComponent2));
321export { MultiSlider };
322function getLabelPrecision(_a) {
323 var labelPrecision = _a.labelPrecision, _b = _a.stepSize, stepSize = _b === void 0 ? MultiSlider.defaultSliderProps.stepSize : _b;
324 // infer default label precision from stepSize because that's how much the handle moves.
325 return labelPrecision == null ? Utils.countDecimalPlaces(stepSize) : labelPrecision;
326}
327function getSortedInteractiveHandleProps(props) {
328 return getSortedHandleProps(props, function (childProps) { return childProps.interactionKind !== HandleInteractionKind.NONE; });
329}
330function getSortedHandleProps(_a, predicate) {
331 var children = _a.children;
332 if (predicate === void 0) { predicate = function () { return true; }; }
333 var maybeHandles = React.Children.map(children, function (child) {
334 return Utils.isElementOfType(child, MultiSlider.Handle) && predicate(child.props) ? child.props : null;
335 });
336 var handles = maybeHandles != null ? maybeHandles : [];
337 handles = handles.filter(function (handle) { return handle !== null; });
338 handles.sort(function (left, right) { return left.value - right.value; });
339 return handles;
340}
341//# sourceMappingURL=multiSlider.js.map
\No newline at end of file