function _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); } function _slicedToArray(arr, i) { return _arrayWithHoles(arr) || _iterableToArrayLimit(arr, i) || _unsupportedIterableToArray(arr, i) || _nonIterableRest(); } function _nonIterableRest() { throw new TypeError("Invalid attempt to destructure non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); } function _unsupportedIterableToArray(o, minLen) { if (!o) return; if (typeof o === "string") return _arrayLikeToArray(o, minLen); var n = Object.prototype.toString.call(o).slice(8, -1); if (n === "Object" && o.constructor) n = o.constructor.name; if (n === "Map" || n === "Set") return Array.from(o); if (n === "Arguments" || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)) return _arrayLikeToArray(o, minLen); } function _arrayLikeToArray(arr, len) { if (len == null || len > arr.length) len = arr.length; for (var i = 0, arr2 = new Array(len); i < len; i++) { arr2[i] = arr[i]; } return arr2; } function _iterableToArrayLimit(arr, i) { if (typeof Symbol === "undefined" || !(Symbol.iterator in Object(arr))) return; var _arr = []; var _n = true; var _d = false; var _e = undefined; try { for (var _i = arr[Symbol.iterator](), _s; !(_n = (_s = _i.next()).done); _n = true) { _arr.push(_s.value); if (i && _arr.length === i) break; } } catch (err) { _d = true; _e = err; } finally { try { if (!_n && _i["return"] != null) _i["return"](); } finally { if (_d) throw _e; } } return _arr; } function _arrayWithHoles(arr) { if (Array.isArray(arr)) return arr; } /** * * TimePicker * */ import React, { useCallback, useEffect, useState, useRef, useMemo } from 'react'; import { isInteger, toNumber } from 'lodash'; import PropTypes from 'prop-types'; import { IconWrapper, TimePicker as StyledTimePicker, TimePickerWrapper, TimeList } from '@buffetjs/styles'; import { useEventListener, useShortcutEffect } from '@buffetjs/hooks'; import Icon from '../Icon'; var MINUTES_IN_HOUR = 60; // Returns string with two digits padded at start with 0 var pad = function pad(num) { return "0".concat(num).substr(-2); }; // Convert time array to formatted time string export var timeFormatter = function timeFormatter(time) { var newTime = Array(3).fill('00').concat(splitArray(time)).reverse(); newTime.length = 3; return format(newTime).join(':'); }; // Convert time string to time array var splitArray = function splitArray(string) { if (isInteger(toNumber(string)) && string) { var stringFormat = string.length === 3 ? "0".concat(string) : string; return stringFormat.match(/.{1,2}/g).reverse(); } var lowercase = string ? string.toLowerCase() : '0'; var array = lowercase.includes('h') ? lowercase.split('h') : lowercase.split(':'); return array.reverse().filter(function (v) { return !!v; }); }; // Ensure two-digit format for minutes and seconds var format = function format(array) { return array.map(function (string, i) { if (string.length < 2) { return i === 0 ? "0".concat(string) : "".concat(string, "0"); } return string; }); }; // Hide seconds if needed var _short = function _short(hour) { var array = hour.split(':'); if (array.length > 2) { return array.slice(0, -1).join(':'); } return hour; }; // return array of minutes in hours with current step var getMinutesArr = function getMinutesArr(step) { var length = MINUTES_IN_HOUR / step; return Array.from({ length: length }, function (_v, i) { return step * i; }); }; // Generate options for TimeList display var getOptions = function getOptions(step) { var hours = Array.from({ length: 24 }, function (_, i) { return i; }); var minutes = getMinutesArr(step); var options = hours.reduce(function (acc, cur) { var hour = pad(cur); var hourOptions = minutes.map(function (minute) { var label = "".concat(hour, ":").concat(pad(minute)); return { value: "".concat(label, ":00"), label: label }; }); return acc.concat(hourOptions); }, []); return options; }; // Find the nearest time option to select a TimeList value var roundHour = function roundHour(time, step) { var arr = splitArray(time); var minutesArr = getMinutesArr(step); var nearMin = nearest(minutesArr.concat(MINUTES_IN_HOUR), parseInt(arr[1], 10)); arr[1] = minutesArr.includes(arr[1]) ? '00' : pad(nearMin); arr[2] = nearMin === 60 ? "".concat(parseInt(arr[2], 10) + 1) : arr[2]; return format(arr.reverse()).join(':'); }; // Set the nearest option to select a TimeList value var nearest = function nearest(arr, val) { return arr.reduce(function (p, n) { return Math.abs(p) > Math.abs(n - val) ? n - val : p; }, Infinity) + val; }; function TimePicker(props) { var name = props.name, onChange = props.onChange, seconds = props.seconds, tabIndex = props.tabIndex, value = props.value, step = props.step; var _useState = useState(''), _useState2 = _slicedToArray(_useState, 2), inputVal = _useState2[0], setInputVal = _useState2[1]; var _useState3 = useState(false), _useState4 = _slicedToArray(_useState3, 2), isOpen = _useState4[0], setIsOpen = _useState4[1]; var options = useMemo(function () { return getOptions(step); }, [step]); var inputRef = useRef(); var wrapperRef = useRef(); var listRef = useRef(); var listRefs = options.reduce(function (acc, curr) { acc[curr.value] = useRef(); return acc; }, {}); var currentTimeSelected = useMemo(function () { return roundHour(timeFormatter(inputVal), step); }, [inputVal, step]); // Effect to set the time useEffect(function () { if (!isOpen) { var time = seconds ? value : _short(value); setInputVal(time); } }, [value, seconds, isOpen]); // Effect to enable scrolling useEffect(function () { var currentRef = currentTimeSelected; if (isOpen && listRefs[currentRef]) { listRef.current.scrollTop = listRefs[currentRef].current.offsetTop; } }, [isOpen, currentTimeSelected, listRefs]); // Custom hook to close the TimeList useEventListener('click', function (event) { if (!wrapperRef.current.contains(event.target)) { setIsOpen(false); } }, isOpen); // Custom hook to select a time using the keyboard's up arrow useShortcutEffect('arrowUp', function () { if (isOpen) { var currentIndex = options.findIndex(function (o) { return o.value === currentTimeSelected; }); if (!currentIndex) return; var nextIndex = currentIndex - 1; var nextTime = options[nextIndex] || options[currentIndex]; updateTime(nextTime.value); } }, isOpen); // Custom hook to select a time using the keyboard's down arrow useShortcutEffect('arrowDown', function () { if (isOpen) { var currentIndex = options.findIndex(function (o) { return o.value === currentTimeSelected; }); var lastIndex = options.length - 1; if (currentIndex >= lastIndex) return; var nextIndex = currentIndex + 1; var nextTime = options[nextIndex] || options[lastIndex]; updateTime(nextTime.value); } }, isOpen); // Custom hook to close the time list useShortcutEffect('enter', function () { if (isOpen) { setIsOpen(false); inputRef.current.blur(); } }, isOpen); useShortcutEffect('tab', function () { if (isOpen) { setIsOpen(false); inputRef.current.blur(); } }, isOpen); var handleChange = function handleChange(_ref) { var target = _ref.target; updateTime(target.value); }; var handleChangeRadio = useCallback(function () {}, []); var formatInputValue = function formatInputValue(time) { if (!seconds) { setInputVal(_short(time)); } else { setInputVal(time); } }; var handleClick = function handleClick(_ref2) { var target = _ref2.target; updateTime(target.value); setIsOpen(false); }; var updateTime = function updateTime(time) { formatInputValue(time); onChange({ target: { name: name, type: 'time', value: timeFormatter(time) } }); }; return /*#__PURE__*/React.createElement(TimePickerWrapper, { ref: wrapperRef, className: props.className }, /*#__PURE__*/React.createElement(StyledTimePicker, _extends({}, props, { autoComplete: "off", onChange: handleChange, onFocus: function onFocus() { return setIsOpen(true); }, ref: inputRef, type: "text", value: inputVal, tabIndex: tabIndex })), /*#__PURE__*/React.createElement(IconWrapper, null, /*#__PURE__*/React.createElement(Icon, { icon: "time" })), /*#__PURE__*/React.createElement(TimeList, { className: isOpen && 'displayed', ref: listRef }, isOpen && options.map(function (option) { return /*#__PURE__*/React.createElement("li", { key: option.value, ref: listRefs[option.value] }, /*#__PURE__*/React.createElement("input", { type: "radio", onChange: handleChangeRadio, onClick: handleClick, value: option.value, id: option.value, name: "time", checked: option.value === currentTimeSelected, tabIndex: "0" }), /*#__PURE__*/React.createElement("label", { htmlFor: option.value }, option.label)); }))); } TimePicker.defaultProps = { className: null, onChange: function onChange() {}, tabIndex: '0', seconds: false, value: '', step: 30 }; TimePicker.propTypes = { className: PropTypes.string, name: PropTypes.string.isRequired, onChange: PropTypes.func, seconds: PropTypes.bool, step: function step(props, propName) { return MINUTES_IN_HOUR % props[propName] > 0 && new Error('step should be divisible by 60'); }, tabIndex: PropTypes.string, value: PropTypes.string }; export default TimePicker;