UNPKG

17 kBJavaScriptView Raw
1"use strict";
2
3exports.__esModule = true;
4exports.default = void 0;
5
6var _classnames = _interopRequireDefault(require("classnames"));
7
8var _querySelectorAll = _interopRequireDefault(require("dom-helpers/querySelectorAll"));
9
10var _propTypes = _interopRequireDefault(require("prop-types"));
11
12var _react = _interopRequireWildcard(require("react"));
13
14var _uncontrollable = require("uncontrollable");
15
16var _Button = _interopRequireDefault(require("./Button"));
17
18var _DateTimePartInput = _interopRequireDefault(require("./DateTimePartInput"));
19
20var _Icon = require("./Icon");
21
22var _Widget = _interopRequireDefault(require("./Widget"));
23
24var _dates = _interopRequireDefault(require("./dates"));
25
26var _useFocusManager = _interopRequireDefault(require("./useFocusManager"));
27
28const _excluded = ["value", "use12HourClock", "padValues", "emptyCharacter", "precision", "noClearButton", "hoursAddon", "minutesAddon", "secondsAddon", "millisecondsAddon", "className", "disabled", "readOnly", "datePart", "onChange", "onBlur", "onFocus"];
29
30function _getRequireWildcardCache(nodeInterop) { if (typeof WeakMap !== "function") return null; var cacheBabelInterop = new WeakMap(); var cacheNodeInterop = new WeakMap(); return (_getRequireWildcardCache = function (nodeInterop) { return nodeInterop ? cacheNodeInterop : cacheBabelInterop; })(nodeInterop); }
31
32function _interopRequireWildcard(obj, nodeInterop) { if (!nodeInterop && obj && obj.__esModule) { return obj; } if (obj === null || typeof obj !== "object" && typeof obj !== "function") { return { default: obj }; } var cache = _getRequireWildcardCache(nodeInterop); if (cache && cache.has(obj)) { return cache.get(obj); } var newObj = {}; var hasPropertyDescriptor = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var key in obj) { if (key !== "default" && Object.prototype.hasOwnProperty.call(obj, key)) { var desc = hasPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : null; if (desc && (desc.get || desc.set)) { Object.defineProperty(newObj, key, desc); } else { newObj[key] = obj[key]; } } } newObj.default = obj; if (cache) { cache.set(obj, newObj); } return newObj; }
33
34function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
35
36function _extends() { _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; return _extends.apply(this, arguments); }
37
38function _objectWithoutPropertiesLoose(source, excluded) { if (source == null) return {}; var target = {}; var sourceKeys = Object.keys(source); var key, i; for (i = 0; i < sourceKeys.length; i++) { key = sourceKeys[i]; if (excluded.indexOf(key) >= 0) continue; target[key] = source[key]; } return target; }
39
40const selectTextRange = el => {
41 if (el instanceof HTMLInputElement) return el.select();
42 const range = document.createRange();
43 range.selectNodeContents(el);
44 const selection = window.getSelection();
45
46 if (selection) {
47 selection.removeAllRanges();
48 selection.addRange(range);
49 }
50}; // prettier-ignore
51
52
53const isEmptyValue = (p, precision) => p.hours == null && p.minutes == null && (precision != 'seconds' && precision !== 'milliseconds' || p.seconds == null) && (precision !== 'milliseconds' || p.milliseconds == null); // prettier-ignore
54
55
56const isPartialValue = (p, precision) => p.hours == null || p.minutes == null || (precision === 'seconds' || precision === 'milliseconds') && p.seconds == null || precision === 'milliseconds' && p.milliseconds == null;
57
58const getValueParts = (value, use12HourClock) => {
59 let hours, minutes, seconds, milliseconds;
60 let meridiem = 'AM';
61
62 if (value) {
63 hours = value.getHours();
64
65 if (use12HourClock) {
66 meridiem = hours < 12 ? 'AM' : 'PM';
67 hours = hours % 12 || 12;
68 }
69
70 minutes = value.getMinutes();
71 seconds = value.getSeconds();
72 milliseconds = value.getMilliseconds();
73 }
74
75 return {
76 hours,
77 minutes,
78 seconds,
79 milliseconds,
80 meridiem
81 };
82};
83
84const TEST_VALID = {
85 hours: /^([1]?[0-9]|2[0-3])$/,
86 hours12: /^(1[0-2]|0?[1-9])$/,
87 minutes: /^([0-5]?\d)$/,
88 seconds: /^([0-5]?\d)$/,
89 milliseconds: /^(\d{1,3})$/
90};
91const TEST_COMPLETE = {
92 hours: /^([3-9]|\d{2})$/,
93 hours12: /^(\d{2}|[2-9])$/,
94 minutes: /^(d{2}|[6-9])$/,
95 seconds: /^(d{2}|[6-9])$/,
96 milliseconds: /^(\d{3})$/
97};
98
99function testPart(value, part, use12HourClock, tests) {
100 const key = part === 'hours' && use12HourClock ? 'hours12' : part;
101 return tests[key].test(value);
102}
103
104const isValid = (value, part, use12HourClock) => testPart(value, part, use12HourClock, TEST_VALID);
105
106const isComplete = (value, part, use12HourClock) => testPart(value, part, use12HourClock, TEST_COMPLETE);
107
108const propTypes = {
109 /**
110 * @example ['valuePicker', [ ['new Date()'] ]]
111 */
112 value: _propTypes.default.instanceOf(Date),
113
114 /**
115 * @example ['onChangePicker', [ ['new Date()'] ]]
116 */
117 onChange: _propTypes.default.func,
118
119 /**
120 * The default date used to construct a new time when the `value` is empty
121 *
122 * @default new Date()
123 **/
124 datePart: _propTypes.default.instanceOf(Date),
125
126 /**
127 * Use a 12 hour clock (with AM/PM) instead of 24 hour one.
128 * The configured localizer may provide a default value .
129 **/
130 use12HourClock: _propTypes.default.bool,
131
132 /** Time part values will be padded by `0` */
133 padValues: _propTypes.default.bool,
134
135 /** The string character used to pad empty, or cleared values */
136 emptyCharacter: _propTypes.default.string,
137
138 /** Hide the input clear button */
139 noClearButton: _propTypes.default.bool,
140
141 /**
142 * @example ['disabled', ['new Date()']]
143 */
144 disabled: _propTypes.default.bool,
145
146 /**
147 * @example ['readOnly', ['new Date()']]
148 */
149 readOnly: _propTypes.default.bool,
150
151 /** Controls how precise of a time can be input **/
152 precision: _propTypes.default.oneOf(['minutes', 'seconds', 'milliseconds']).isRequired,
153
154 /**
155 * The seperator between hours and minutes
156 * @default ':'
157 */
158 hoursAddon: _propTypes.default.node,
159
160 /**
161 * The seperator between hours and minutes
162 * @default ':'
163 */
164 minutesAddon: _propTypes.default.node,
165
166 /**
167 * The seperator between hours and minutes
168 * @default ':'
169 */
170 secondsAddon: _propTypes.default.node,
171
172 /**
173 * The seperator between hours and minutes
174 * @default '.'
175 */
176 millisecondsAddon: _propTypes.default.node
177};
178const defaultProps = {
179 hoursAddon: ':',
180 padValues: true,
181 precision: 'minutes',
182 emptyCharacter: '-'
183};
184
185// let count = 0
186function useTimePartState(value, use12HourClock) {
187 const [state, setState] = (0, _react.useState)(() => ({
188 value,
189 use12HourClock,
190 timeParts: getValueParts(value, use12HourClock)
191 }));
192 const setTimeParts = (0, _react.useCallback)(timeParts => setState(s => Object.assign({}, s, {
193 timeParts
194 })), [setState]);
195
196 if (state.value !== value || state.use12HourClock !== use12HourClock) {
197 // count++
198 // if (count < 100)
199 setState({
200 value,
201 use12HourClock,
202 timeParts: getValueParts(value, use12HourClock)
203 });
204 }
205
206 return [state.timeParts, setTimeParts];
207}
208
209function TimeInput(uncontrolledProps) {
210 const _useUncontrolled = (0, _uncontrollable.useUncontrolled)(uncontrolledProps, {
211 value: 'onChange'
212 }),
213 {
214 value,
215 use12HourClock,
216 padValues: pad,
217 emptyCharacter,
218 precision,
219 noClearButton,
220 hoursAddon,
221 minutesAddon,
222 secondsAddon,
223 millisecondsAddon,
224 className,
225 disabled,
226 readOnly,
227 datePart,
228 onChange,
229 onBlur,
230 onFocus
231 } = _useUncontrolled,
232 props = _objectWithoutPropertiesLoose(_useUncontrolled, _excluded);
233
234 let minsAddon = minutesAddon !== undefined ? minutesAddon : precision === 'seconds' || precision === 'milliseconds' ? ':' : '';
235 let secsAddon = secondsAddon !== undefined ? secondsAddon : precision === 'milliseconds' ? ':' : '';
236 const ref = (0, _react.useRef)(null);
237 const hourRef = (0, _react.useRef)(null);
238 const [focusEvents, focused] = (0, _useFocusManager.default)(ref, {
239 disabled,
240 onBlur,
241 onFocus
242 }, {
243 didHandle: (focused, e) => {
244 var _hourRef$current;
245
246 if (!focused) return;
247 if (!e.target.dataset.focusable) (_hourRef$current = hourRef.current) == null ? void 0 : _hourRef$current.focus();else select(e.target);
248 }
249 });
250 const [timeParts, setTimeParts] = useTimePartState(value != null ? value : null, use12HourClock != null ? use12HourClock : false);
251
252 function getDatePart() {
253 return _dates.default.startOf(datePart || new Date(), 'day');
254 }
255
256 const getMin = part => part === 'hours' ? 1 : 0;
257
258 const getMax = part => {
259 if (part === 'hours') return use12HourClock ? 12 : 23;
260 if (part === 'milliseconds') return 999;
261 return 59;
262 };
263
264 function select(target = document.activeElement) {
265 window.Promise.resolve().then(() => {
266 if (focused) selectTextRange(target);
267 });
268 }
269 /**
270 * Handlers
271 */
272
273
274 const handleClear = () => {
275 var _hourRef$current2;
276
277 (_hourRef$current2 = hourRef.current) == null ? void 0 : _hourRef$current2.focus();
278 if (value) onChange(null);else setTimeParts(getValueParts(null));
279 };
280
281 const handleChange = (part, event) => {
282 const currentValue = timeParts[part];
283 const {
284 target
285 } = event;
286 const rawValue = target.value;
287 let strValue = `${currentValue || ''}${rawValue}`;
288 let numValue = +strValue;
289
290 if (isNaN(numValue) || strValue && !isValid(strValue, part, use12HourClock != null ? use12HourClock : false)) {
291 // the combined value is now past the max or invalid so try the single
292 // digit and "start over" filling the value
293 if (isValid(rawValue, part, use12HourClock != null ? use12HourClock : false) && !isNaN(+rawValue)) {
294 // change the effective current value
295 strValue = rawValue;
296 numValue = +rawValue;
297 } else {
298 return event.preventDefault();
299 }
300 }
301
302 const nextValue = target.value ? numValue : null;
303 notifyChange({
304 [part]: nextValue
305 });
306
307 if (nextValue != null && isComplete(strValue, part, use12HourClock != null ? use12HourClock : false)) {
308 focusNext(event.currentTarget, +1);
309 } else {
310 select(target);
311 }
312 };
313
314 const handleSelect = ({
315 target
316 }) => {
317 select(target);
318 };
319
320 const handleKeyDown = (part, event) => {
321 const {
322 key
323 } = event;
324 const input = event.currentTarget;
325 const {
326 selectionStart: start,
327 selectionEnd: end
328 } = input;
329 const isRTL = getComputedStyle(input).getPropertyValue('direction') === 'rtl';
330 const isMeridiem = part === 'meridiem';
331 const isNext = key === (isRTL ? 'ArrowLeft' : 'ArrowRight');
332 const isPrev = key === (isRTL ? 'ArrowRight' : 'ArrowLeft');
333
334 if (key === 'ArrowUp') {
335 event.preventDefault();
336 increment(part, 1);
337 }
338
339 if (key === 'ArrowDown') {
340 event.preventDefault();
341 increment(part, -1);
342 }
343
344 if (isPrev && (isMeridiem || start - 1 < 0)) {
345 event.preventDefault();
346 focusNext(input, -1);
347 }
348
349 if (isNext && (isMeridiem || input.value.length <= end + 1)) {
350 event.preventDefault();
351 focusNext(input, +1);
352 }
353
354 if (readOnly && key !== 'Tab') {
355 event.preventDefault();
356 }
357
358 if (isMeridiem) {
359 if (key === 'a' || key === 'A') notifyChange({
360 meridiem: 'AM'
361 });
362 if (key === 'p' || key === 'P') notifyChange({
363 meridiem: 'PM'
364 });
365 }
366 };
367
368 const increment = (part, inc) => {
369 let nextPart = timeParts[part];
370
371 if (part === 'meridiem') {
372 nextPart = nextPart === 'AM' ? 'PM' : 'AM';
373 } else {
374 nextPart = (nextPart || 0) + inc;
375 if (!isValid(String(nextPart), part, use12HourClock != null ? use12HourClock : false)) return;
376 }
377
378 notifyChange({
379 [part]: nextPart
380 });
381 select();
382 };
383
384 function notifyChange(updates) {
385 const nextTimeParts = Object.assign({}, timeParts, updates);
386
387 if (value && isEmptyValue(nextTimeParts, precision)) {
388 return onChange(null);
389 }
390
391 if (isPartialValue(nextTimeParts, precision)) return setTimeParts(nextTimeParts);
392 let {
393 hours,
394 minutes,
395 seconds,
396 milliseconds,
397 meridiem
398 } = nextTimeParts;
399 let nextDate = new Date(value || getDatePart());
400
401 if (use12HourClock) {
402 if (hours === 12) hours = 0;
403 hours += meridiem === 'PM' ? 12 : 0;
404 }
405
406 nextDate.setHours(hours);
407 nextDate.setMinutes(minutes);
408 if (seconds != null) nextDate.setSeconds(seconds);
409 if (milliseconds != null) nextDate.setMilliseconds(milliseconds);
410 onChange(nextDate, {
411 lastValue: value,
412 timeParts
413 });
414 }
415
416 function focusNext(input, delta) {
417 let nodes = (0, _querySelectorAll.default)(ref.current, '* [data-focusable]');
418 let next = nodes[nodes.indexOf(input) + delta];
419 next == null ? void 0 : next.focus();
420 select(next);
421 }
422
423 const {
424 hours,
425 minutes,
426 seconds,
427 milliseconds,
428 meridiem
429 } = timeParts;
430 const showClear = !isEmptyValue(timeParts, precision);
431 return /*#__PURE__*/_react.default.createElement(_Widget.default, _extends({}, props, {
432 role: "group",
433 ref: ref
434 }, focusEvents, {
435 focused: focused,
436 disabled: disabled,
437 readOnly: readOnly,
438 className: (0, _classnames.default)(className, 'rw-time-input rw-widget-input')
439 }), /*#__PURE__*/_react.default.createElement(_DateTimePartInput.default, {
440 size: 2,
441 pad: pad ? 2 : undefined,
442 value: hours,
443 disabled: disabled,
444 readOnly: readOnly,
445 "aria-label": "hours",
446 min: getMin('hours'),
447 max: getMax('hours'),
448 ref: hourRef,
449 emptyChar: emptyCharacter,
450 onSelect: handleSelect,
451 onChange: e => handleChange('hours', e),
452 onKeyDown: e => handleKeyDown('hours', e)
453 }), hoursAddon && /*#__PURE__*/_react.default.createElement("span", null, hoursAddon), /*#__PURE__*/_react.default.createElement(_DateTimePartInput.default, {
454 size: 2,
455 pad: pad ? 2 : undefined,
456 value: minutes,
457 disabled: disabled,
458 readOnly: readOnly,
459 "aria-label": "minutes",
460 min: getMin('minutes'),
461 max: getMax('minutes'),
462 emptyChar: emptyCharacter,
463 onSelect: handleSelect,
464 onChange: e => handleChange('minutes', e),
465 onKeyDown: e => handleKeyDown('minutes', e)
466 }), minsAddon && /*#__PURE__*/_react.default.createElement("span", null, minsAddon), (precision === 'seconds' || precision === 'milliseconds') && /*#__PURE__*/_react.default.createElement(_react.default.Fragment, null, /*#__PURE__*/_react.default.createElement(_DateTimePartInput.default, {
467 size: 2,
468 pad: pad ? 2 : undefined,
469 value: seconds,
470 disabled: disabled,
471 readOnly: readOnly,
472 "aria-label": "seconds",
473 min: getMin('seconds'),
474 max: getMax('seconds'),
475 emptyChar: emptyCharacter,
476 onSelect: handleSelect,
477 onChange: e => handleChange('seconds', e),
478 onKeyDown: e => handleKeyDown('seconds', e)
479 }), secsAddon && /*#__PURE__*/_react.default.createElement("span", null, secsAddon)), precision === 'milliseconds' && /*#__PURE__*/_react.default.createElement(_react.default.Fragment, null, /*#__PURE__*/_react.default.createElement(_DateTimePartInput.default, {
480 size: 3,
481 pad: pad ? 3 : undefined,
482 value: milliseconds,
483 disabled: disabled,
484 readOnly: readOnly,
485 "aria-label": "milliseconds",
486 min: getMin('milliseconds'),
487 max: getMax('milliseconds'),
488 emptyChar: emptyCharacter,
489 onSelect: handleSelect,
490 onChange: e => handleChange('milliseconds', e),
491 onKeyDown: e => handleKeyDown('milliseconds', e)
492 }), millisecondsAddon && /*#__PURE__*/_react.default.createElement("span", null, millisecondsAddon)), use12HourClock && /*#__PURE__*/_react.default.createElement("div", {
493 role: "listbox",
494 "aria-label": "AM/PM",
495 "aria-disabled": disabled,
496 "aria-readonly": readOnly,
497 className: "rw-time-part-meridiem"
498 }, /*#__PURE__*/_react.default.createElement("div", {
499 "data-focusable": true,
500 role: "option",
501 "aria-atomic": true,
502 "aria-selected": true,
503 "aria-setsize": 2,
504 "aria-live": "assertive",
505 "aria-disabled": disabled,
506 "aria-readonly": readOnly,
507 "aria-posinset": meridiem === 'AM' ? 1 : 2,
508 tabIndex: !disabled ? 0 : void 0,
509 onFocus: handleSelect,
510 onSelect: handleSelect,
511 onKeyDown: e => handleKeyDown('meridiem', e)
512 }, /*#__PURE__*/_react.default.createElement("abbr", null, meridiem))), !noClearButton && /*#__PURE__*/_react.default.createElement(_Button.default, {
513 label: 'clear input',
514 onClick: handleClear,
515 disabled: disabled || readOnly,
516 className: (0, _classnames.default)('rw-time-input-clear', showClear && 'rw-show')
517 }, _Icon.times));
518}
519
520TimeInput.propTypes = propTypes;
521TimeInput.defaultProps = defaultProps;
522var _default = TimeInput;
523exports.default = _default;
\No newline at end of file