UNPKG

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