UNPKG

13.1 kBJavaScriptView Raw
1/*
2 * Copyright 2015 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 classNames from "classnames";
17import * as React from "react";
18import { Classes as CoreClasses, DISPLAYNAME_PREFIX, HTMLSelect, Icon, Intent, Keys } from "@blueprintjs/core";
19import * as Classes from "./common/classes";
20import * as DateUtils from "./common/dateUtils";
21import { getDefaultMaxTime, getDefaultMinTime, getTimeUnit, getTimeUnitClassName, isTimeUnitValid, setTimeUnit, TimeUnit, wrapTimeAtUnit, } from "./common/timeUnit";
22import * as Utils from "./common/utils";
23export const TimePrecision = {
24 MILLISECOND: "millisecond",
25 MINUTE: "minute",
26 SECOND: "second",
27};
28export class TimePicker extends React.Component {
29 static defaultProps = {
30 autoFocus: false,
31 disabled: false,
32 maxTime: getDefaultMaxTime(),
33 minTime: getDefaultMinTime(),
34 precision: TimePrecision.MINUTE,
35 selectAllOnFocus: false,
36 showArrowButtons: false,
37 useAmPm: false,
38 };
39 static displayName = `${DISPLAYNAME_PREFIX}.TimePicker`;
40 constructor(props, context) {
41 super(props, context);
42 this.state = this.getFullStateFromValue(this.getInitialValue(), props.useAmPm);
43 }
44 render() {
45 const shouldRenderMilliseconds = this.props.precision === TimePrecision.MILLISECOND;
46 const shouldRenderSeconds = shouldRenderMilliseconds || this.props.precision === TimePrecision.SECOND;
47 const hourUnit = this.props.useAmPm ? TimeUnit.HOUR_12 : TimeUnit.HOUR_24;
48 const classes = classNames(Classes.TIMEPICKER, this.props.className, {
49 [CoreClasses.DISABLED]: this.props.disabled,
50 });
51 return (React.createElement("div", { className: classes },
52 React.createElement("div", { className: Classes.TIMEPICKER_ARROW_ROW },
53 this.maybeRenderArrowButton(true, hourUnit),
54 this.maybeRenderArrowButton(true, TimeUnit.MINUTE),
55 shouldRenderSeconds && this.maybeRenderArrowButton(true, TimeUnit.SECOND),
56 shouldRenderMilliseconds && this.maybeRenderArrowButton(true, TimeUnit.MS)),
57 React.createElement("div", { className: Classes.TIMEPICKER_INPUT_ROW },
58 this.renderInput(Classes.TIMEPICKER_HOUR, hourUnit, this.state.hourText),
59 this.renderDivider(),
60 this.renderInput(Classes.TIMEPICKER_MINUTE, TimeUnit.MINUTE, this.state.minuteText),
61 shouldRenderSeconds && this.renderDivider(),
62 shouldRenderSeconds &&
63 this.renderInput(Classes.TIMEPICKER_SECOND, TimeUnit.SECOND, this.state.secondText),
64 shouldRenderMilliseconds && this.renderDivider("."),
65 shouldRenderMilliseconds &&
66 this.renderInput(Classes.TIMEPICKER_MILLISECOND, TimeUnit.MS, this.state.millisecondText)),
67 this.maybeRenderAmPm(),
68 React.createElement("div", { className: Classes.TIMEPICKER_ARROW_ROW },
69 this.maybeRenderArrowButton(false, hourUnit),
70 this.maybeRenderArrowButton(false, TimeUnit.MINUTE),
71 shouldRenderSeconds && this.maybeRenderArrowButton(false, TimeUnit.SECOND),
72 shouldRenderMilliseconds && this.maybeRenderArrowButton(false, TimeUnit.MS))));
73 }
74 componentDidUpdate(prevProps) {
75 const didMinTimeChange = prevProps.minTime !== this.props.minTime;
76 const didMaxTimeChange = prevProps.maxTime !== this.props.maxTime;
77 const didBoundsChange = didMinTimeChange || didMaxTimeChange;
78 const didPropValueChange = prevProps.value !== this.props.value;
79 const shouldStateUpdate = didBoundsChange || didPropValueChange;
80 let value = this.state.value;
81 if (this.props.value == null) {
82 value = this.getInitialValue();
83 }
84 if (didBoundsChange) {
85 value = DateUtils.getTimeInRange(this.state.value, this.props.minTime, this.props.maxTime);
86 }
87 if (this.props.value != null && !DateUtils.areSameTime(this.props.value, prevProps.value)) {
88 value = this.props.value;
89 }
90 if (shouldStateUpdate) {
91 this.setState(this.getFullStateFromValue(value, this.props.useAmPm));
92 }
93 }
94 // begin method definitions: rendering
95 maybeRenderArrowButton(isDirectionUp, timeUnit) {
96 if (!this.props.showArrowButtons) {
97 return null;
98 }
99 const classes = classNames(Classes.TIMEPICKER_ARROW_BUTTON, getTimeUnitClassName(timeUnit));
100 const onClick = () => (isDirectionUp ? this.incrementTime : this.decrementTime)(timeUnit);
101 // set tabIndex=-1 to ensure a valid FocusEvent relatedTarget when focused
102 return (React.createElement("span", { tabIndex: -1, className: classes, onClick: onClick },
103 React.createElement(Icon, { icon: isDirectionUp ? "chevron-up" : "chevron-down", title: isDirectionUp ? "Increase" : "Decrease" })));
104 }
105 renderDivider(text = ":") {
106 return React.createElement("span", { className: Classes.TIMEPICKER_DIVIDER_TEXT }, text);
107 }
108 renderInput(className, unit, value) {
109 const isValid = isTimeUnitValid(unit, parseInt(value, 10));
110 const isHour = unit === TimeUnit.HOUR_12 || unit === TimeUnit.HOUR_24;
111 return (React.createElement("input", { className: classNames(Classes.TIMEPICKER_INPUT, { [CoreClasses.intentClass(Intent.DANGER)]: !isValid }, className), onBlur: this.getInputBlurHandler(unit), onChange: this.getInputChangeHandler(unit), onFocus: this.getInputFocusHandler(unit), onKeyDown: this.getInputKeyDownHandler(unit), onKeyUp: this.getInputKeyUpHandler(unit), value: value, disabled: this.props.disabled, autoFocus: isHour && this.props.autoFocus }));
112 }
113 maybeRenderAmPm() {
114 if (!this.props.useAmPm) {
115 return null;
116 }
117 return (React.createElement(HTMLSelect, { className: Classes.TIMEPICKER_AMPM_SELECT, disabled: this.props.disabled, onChange: this.handleAmPmChange, value: this.state.isPm ? "pm" : "am" },
118 React.createElement("option", { value: "am" }, "AM"),
119 React.createElement("option", { value: "pm" }, "PM")));
120 }
121 // begin method definitions: event handlers
122 getInputChangeHandler = (unit) => (e) => {
123 const text = getStringValueFromInputEvent(e);
124 switch (unit) {
125 case TimeUnit.HOUR_12:
126 case TimeUnit.HOUR_24:
127 this.setState({ hourText: text });
128 break;
129 case TimeUnit.MINUTE:
130 this.setState({ minuteText: text });
131 break;
132 case TimeUnit.SECOND:
133 this.setState({ secondText: text });
134 break;
135 case TimeUnit.MS:
136 this.setState({ millisecondText: text });
137 break;
138 }
139 };
140 getInputBlurHandler = (unit) => (e) => {
141 const text = getStringValueFromInputEvent(e);
142 this.updateTime(parseInt(text, 10), unit);
143 this.props.onBlur?.(e, unit);
144 };
145 getInputFocusHandler = (unit) => (e) => {
146 if (this.props.selectAllOnFocus) {
147 e.currentTarget.select();
148 }
149 this.props.onFocus?.(e, unit);
150 };
151 getInputKeyDownHandler = (unit) => (e) => {
152 handleKeyEvent(e, {
153 [Keys.ARROW_UP]: () => this.incrementTime(unit),
154 [Keys.ARROW_DOWN]: () => this.decrementTime(unit),
155 [Keys.ENTER]: () => {
156 e.currentTarget.blur();
157 },
158 });
159 this.props.onKeyDown?.(e, unit);
160 };
161 getInputKeyUpHandler = (unit) => (e) => {
162 this.props.onKeyUp?.(e, unit);
163 };
164 handleAmPmChange = (e) => {
165 const isNextPm = e.currentTarget.value === "pm";
166 if (isNextPm !== this.state.isPm) {
167 const hour = DateUtils.convert24HourMeridiem(this.state.value.getHours(), isNextPm);
168 this.setState({ isPm: isNextPm }, () => this.updateTime(hour, TimeUnit.HOUR_24));
169 }
170 };
171 // begin method definitions: state modification
172 /**
173 * Generates a full ITimePickerState object with all text fields set to formatted strings based on value
174 */
175 getFullStateFromValue(value, useAmPm) {
176 const timeInRange = DateUtils.getTimeInRange(value, this.props.minTime, this.props.maxTime);
177 const hourUnit = useAmPm ? TimeUnit.HOUR_12 : TimeUnit.HOUR_24;
178 /* tslint:disable:object-literal-sort-keys */
179 return {
180 hourText: formatTime(timeInRange.getHours(), hourUnit),
181 minuteText: formatTime(timeInRange.getMinutes(), TimeUnit.MINUTE),
182 secondText: formatTime(timeInRange.getSeconds(), TimeUnit.SECOND),
183 millisecondText: formatTime(timeInRange.getMilliseconds(), TimeUnit.MS),
184 value: timeInRange,
185 isPm: DateUtils.getIsPmFrom24Hour(timeInRange.getHours()),
186 };
187 /* tslint:enable:object-literal-sort-keys */
188 }
189 incrementTime = (unit) => this.shiftTime(unit, 1);
190 decrementTime = (unit) => this.shiftTime(unit, -1);
191 shiftTime(unit, amount) {
192 if (this.props.disabled) {
193 return;
194 }
195 const newTime = getTimeUnit(unit, this.state.value) + amount;
196 this.updateTime(wrapTimeAtUnit(unit, newTime), unit);
197 }
198 updateTime(time, unit) {
199 const newValue = DateUtils.clone(this.state.value);
200 if (isTimeUnitValid(unit, time)) {
201 setTimeUnit(unit, time, newValue, this.state.isPm);
202 if (DateUtils.isTimeInRange(newValue, this.props.minTime, this.props.maxTime)) {
203 this.updateState({ value: newValue });
204 }
205 else {
206 this.updateState(this.getFullStateFromValue(this.state.value, this.props.useAmPm));
207 }
208 }
209 else {
210 this.updateState(this.getFullStateFromValue(this.state.value, this.props.useAmPm));
211 }
212 }
213 updateState(state) {
214 let newState = state;
215 const hasNewValue = newState.value != null && !DateUtils.areSameTime(newState.value, this.state.value);
216 if (this.props.value == null) {
217 // component is uncontrolled
218 if (hasNewValue) {
219 newState = this.getFullStateFromValue(newState.value, this.props.useAmPm);
220 }
221 this.setState(newState);
222 }
223 else {
224 // component is controlled, and there's a new value
225 // so set inputs' text based off of _old_ value and later fire onChange with new value
226 if (hasNewValue) {
227 this.setState(this.getFullStateFromValue(this.state.value, this.props.useAmPm));
228 }
229 else {
230 // no new value, this means only text has changed (from user typing)
231 // we want inputs to change, so update state with new text for the inputs
232 // but don't change actual value
233 this.setState({ ...newState, value: DateUtils.clone(this.state.value) });
234 }
235 }
236 if (hasNewValue) {
237 this.props.onChange?.(newState.value);
238 }
239 }
240 getInitialValue() {
241 let value = this.props.minTime;
242 if (this.props.value != null) {
243 value = this.props.value;
244 }
245 else if (this.props.defaultValue != null) {
246 value = this.props.defaultValue;
247 }
248 return value;
249 }
250}
251function formatTime(time, unit) {
252 switch (unit) {
253 case TimeUnit.HOUR_24:
254 return time.toString();
255 case TimeUnit.HOUR_12:
256 return DateUtils.get12HourFrom24Hour(time).toString();
257 case TimeUnit.MINUTE:
258 case TimeUnit.SECOND:
259 return Utils.padWithZeroes(time.toString(), 2);
260 case TimeUnit.MS:
261 return Utils.padWithZeroes(time.toString(), 3);
262 default:
263 throw Error("Invalid TimeUnit");
264 }
265}
266function getStringValueFromInputEvent(e) {
267 return e.target.value;
268}
269function handleKeyEvent(e, actions, preventDefault = true) {
270 for (const k of Object.keys(actions)) {
271 const key = Number(k);
272 // HACKHACK: https://github.com/palantir/blueprint/issues/4165
273 // eslint-disable-next-line deprecation/deprecation
274 if (e.which === key) {
275 if (preventDefault) {
276 e.preventDefault();
277 }
278 actions[key]();
279 }
280 }
281}
282//# sourceMappingURL=timePicker.js.map
\No newline at end of file