UNPKG

11.4 kBJavaScriptView Raw
1/*
2 * Copyright 2016 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 { __decorate, __extends } from "tslib";
17import classNames from "classnames";
18import * as React from "react";
19import { polyfill } from "react-lifecycles-compat";
20import { AbstractPureComponent2, Classes, Keys } from "../../common";
21import { DISPLAYNAME_PREFIX } from "../../common/props";
22import { clamp } from "../../common/utils";
23import { formatPercentage } from "./sliderUtils";
24// props that require number values, for validation
25var NUMBER_PROPS = ["max", "min", "stepSize", "tickSize", "value"];
26/** Internal component for a Handle with click/drag/keyboard logic to determine a new value. */
27var Handle = /** @class */ (function (_super) {
28 __extends(Handle, _super);
29 function Handle() {
30 var _this = _super !== null && _super.apply(this, arguments) || this;
31 _this.state = {
32 isMoving: false,
33 };
34 _this.handleElement = null;
35 _this.refHandlers = {
36 handle: function (el) { return (_this.handleElement = el); },
37 };
38 _this.beginHandleMovement = function (event) {
39 document.addEventListener("mousemove", _this.handleHandleMovement);
40 document.addEventListener("mouseup", _this.endHandleMovement);
41 _this.setState({ isMoving: true });
42 _this.changeValue(_this.clientToValue(_this.mouseEventClientOffset(event)));
43 };
44 _this.beginHandleTouchMovement = function (event) {
45 document.addEventListener("touchmove", _this.handleHandleTouchMovement);
46 document.addEventListener("touchend", _this.endHandleTouchMovement);
47 document.addEventListener("touchcancel", _this.endHandleTouchMovement);
48 _this.setState({ isMoving: true });
49 _this.changeValue(_this.clientToValue(_this.touchEventClientOffset(event)));
50 };
51 _this.getStyleProperties = function () {
52 if (_this.handleElement == null) {
53 return {};
54 }
55 // The handle midpoint of RangeSlider is actually shifted by a margin to
56 // be on the edge of the visible handle element. Because the midpoint
57 // calculation does not take this margin into account, we instead
58 // measure the long side (which is equal to the short side plus the
59 // margin).
60 var _a = _this.props, _b = _a.min, min = _b === void 0 ? 0 : _b, tickSizeRatio = _a.tickSizeRatio, value = _a.value, vertical = _a.vertical;
61 var handleMidpoint = _this.getHandleMidpointAndOffset(_this.handleElement, true).handleMidpoint;
62 var offsetRatio = (value - min) * tickSizeRatio;
63 var offsetCalc = "calc(" + formatPercentage(offsetRatio) + " - " + handleMidpoint + "px)";
64 return vertical ? { bottom: offsetCalc } : { left: offsetCalc };
65 };
66 _this.endHandleMovement = function (event) {
67 _this.handleMoveEndedAt(_this.mouseEventClientOffset(event));
68 };
69 _this.endHandleTouchMovement = function (event) {
70 _this.handleMoveEndedAt(_this.touchEventClientOffset(event));
71 };
72 _this.handleMoveEndedAt = function (clientPixel) {
73 var _a, _b;
74 _this.removeDocumentEventListeners();
75 _this.setState({ isMoving: false });
76 // always invoke onRelease; changeValue may call onChange if value is different
77 var finalValue = _this.changeValue(_this.clientToValue(clientPixel));
78 (_b = (_a = _this.props).onRelease) === null || _b === void 0 ? void 0 : _b.call(_a, finalValue);
79 };
80 _this.handleHandleMovement = function (event) {
81 _this.handleMovedTo(_this.mouseEventClientOffset(event));
82 };
83 _this.handleHandleTouchMovement = function (event) {
84 _this.handleMovedTo(_this.touchEventClientOffset(event));
85 };
86 _this.handleMovedTo = function (clientPixel) {
87 if (_this.state.isMoving && !_this.props.disabled) {
88 _this.changeValue(_this.clientToValue(clientPixel));
89 }
90 };
91 _this.handleKeyDown = function (event) {
92 var _a = _this.props, stepSize = _a.stepSize, value = _a.value;
93 // HACKHACK: https://github.com/palantir/blueprint/issues/4165
94 /* eslint-disable-next-line deprecation/deprecation */
95 var which = event.which;
96 if (which === Keys.ARROW_DOWN || which === Keys.ARROW_LEFT) {
97 _this.changeValue(value - stepSize);
98 // this key event has been handled! prevent browser scroll on up/down
99 event.preventDefault();
100 }
101 else if (which === Keys.ARROW_UP || which === Keys.ARROW_RIGHT) {
102 _this.changeValue(value + stepSize);
103 event.preventDefault();
104 }
105 };
106 _this.handleKeyUp = function (event) {
107 var _a, _b;
108 // HACKHACK: https://github.com/palantir/blueprint/issues/4165
109 /* eslint-disable-next-line deprecation/deprecation */
110 if ([Keys.ARROW_UP, Keys.ARROW_DOWN, Keys.ARROW_LEFT, Keys.ARROW_RIGHT].indexOf(event.which) >= 0) {
111 (_b = (_a = _this.props).onRelease) === null || _b === void 0 ? void 0 : _b.call(_a, _this.props.value);
112 }
113 };
114 return _this;
115 }
116 Handle.prototype.componentDidMount = function () {
117 // The first time this component renders, it has no ref to the handle and thus incorrectly centers the handle.
118 // Therefore, on the first mount, force a re-render to center the handle with the ref'd component.
119 this.forceUpdate();
120 };
121 Handle.prototype.render = function () {
122 var _a;
123 var _b = this.props, className = _b.className, disabled = _b.disabled, label = _b.label;
124 var isMoving = this.state.isMoving;
125 return (React.createElement("span", { className: classNames(Classes.SLIDER_HANDLE, (_a = {}, _a[Classes.ACTIVE] = isMoving, _a), className), onKeyDown: disabled ? undefined : this.handleKeyDown, onKeyUp: disabled ? undefined : this.handleKeyUp, onMouseDown: disabled ? undefined : this.beginHandleMovement, onTouchStart: disabled ? undefined : this.beginHandleTouchMovement, ref: this.refHandlers.handle, style: this.getStyleProperties(), tabIndex: 0 }, label == null ? null : React.createElement("span", { className: Classes.SLIDER_LABEL }, label)));
126 };
127 Handle.prototype.componentWillUnmount = function () {
128 this.removeDocumentEventListeners();
129 };
130 /** Convert client pixel to value between min and max. */
131 Handle.prototype.clientToValue = function (clientPixel) {
132 var _a = this.props, stepSize = _a.stepSize, tickSize = _a.tickSize, value = _a.value, vertical = _a.vertical;
133 if (this.handleElement == null) {
134 return value;
135 }
136 // #1769: this logic doesn't work perfectly when the tick size is
137 // smaller than the handle size; it may be off by a tick or two.
138 var clientPixelNormalized = vertical ? window.innerHeight - clientPixel : clientPixel;
139 var handleCenterPixel = this.getHandleElementCenterPixel(this.handleElement);
140 var pixelDelta = clientPixelNormalized - handleCenterPixel;
141 if (isNaN(pixelDelta)) {
142 return value;
143 }
144 // convert pixels to range value in increments of `stepSize`
145 return value + Math.round(pixelDelta / (tickSize * stepSize)) * stepSize;
146 };
147 Handle.prototype.mouseEventClientOffset = function (event) {
148 return this.props.vertical ? event.clientY : event.clientX;
149 };
150 Handle.prototype.touchEventClientOffset = function (event) {
151 var touch = event.changedTouches[0];
152 return this.props.vertical ? touch.clientY : touch.clientX;
153 };
154 Handle.prototype.validateProps = function (props) {
155 for (var _i = 0, NUMBER_PROPS_1 = NUMBER_PROPS; _i < NUMBER_PROPS_1.length; _i++) {
156 var prop = NUMBER_PROPS_1[_i];
157 if (typeof props[prop] !== "number") {
158 throw new Error("[Blueprint] <Handle> requires number value for " + prop + " prop");
159 }
160 }
161 };
162 /** Clamp value and invoke callback if it differs from current value */
163 Handle.prototype.changeValue = function (newValue, callback) {
164 if (callback === void 0) { callback = this.props.onChange; }
165 newValue = this.clamp(newValue);
166 if (!isNaN(newValue) && this.props.value !== newValue) {
167 callback === null || callback === void 0 ? void 0 : callback(newValue);
168 }
169 return newValue;
170 };
171 /** Clamp value between min and max props */
172 Handle.prototype.clamp = function (value) {
173 return clamp(value, this.props.min, this.props.max);
174 };
175 Handle.prototype.getHandleElementCenterPixel = function (handleElement) {
176 var _a = this.getHandleMidpointAndOffset(handleElement), handleMidpoint = _a.handleMidpoint, handleOffset = _a.handleOffset;
177 return handleOffset + handleMidpoint;
178 };
179 Handle.prototype.getHandleMidpointAndOffset = function (handleElement, useOppositeDimension) {
180 if (useOppositeDimension === void 0) { useOppositeDimension = false; }
181 if (handleElement == null) {
182 return { handleMidpoint: 0, handleOffset: 0 };
183 }
184 var vertical = this.props.vertical;
185 // getBoundingClientRect().height includes border size; clientHeight does not.
186 var handleRect = handleElement.getBoundingClientRect();
187 var sizeKey = vertical
188 ? useOppositeDimension
189 ? "width"
190 : "height"
191 : useOppositeDimension
192 ? "height"
193 : "width";
194 // "bottom" value seems to be consistently incorrect, so explicitly
195 // calculate it using the window offset instead.
196 var handleOffset = vertical ? window.innerHeight - (handleRect.top + handleRect[sizeKey]) : handleRect.left;
197 return { handleMidpoint: handleRect[sizeKey] / 2, handleOffset: handleOffset };
198 };
199 Handle.prototype.removeDocumentEventListeners = function () {
200 document.removeEventListener("mousemove", this.handleHandleMovement);
201 document.removeEventListener("mouseup", this.endHandleMovement);
202 document.removeEventListener("touchmove", this.handleHandleTouchMovement);
203 document.removeEventListener("touchend", this.endHandleTouchMovement);
204 document.removeEventListener("touchcancel", this.endHandleTouchMovement);
205 };
206 Handle.displayName = DISPLAYNAME_PREFIX + ".SliderHandle";
207 Handle = __decorate([
208 polyfill
209 ], Handle);
210 return Handle;
211}(AbstractPureComponent2));
212export { Handle };
213//# sourceMappingURL=handle.js.map
\No newline at end of file