UNPKG

wix-style-react

Version:
485 lines (479 loc) • 19.2 kB
"use strict"; var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault"); exports.__esModule = true; exports.default = exports.DEFAULT_TIME_STYLE = exports.DEFAULT_STEP = void 0; var _extends2 = _interopRequireDefault(require("@babel/runtime/helpers/extends")); var _defineProperty2 = _interopRequireDefault(require("@babel/runtime/helpers/defineProperty")); var _objectWithoutProperties2 = _interopRequireDefault(require("@babel/runtime/helpers/objectWithoutProperties")); var _react = _interopRequireWildcard(require("react")); var _propTypes = _interopRequireDefault(require("prop-types")); var _Input = _interopRequireDefault(require("../Input")); var _DropdownBase = _interopRequireDefault(require("../DropdownBase")); var _constants = require("./constants"); var _TimeInputSt = require("./TimeInput.st.css"); var _wixDesignSystemsLocaleUtils = require("wix-design-systems-locale-utils"); var _context = require("../WixStyleReactEnvironmentProvider/context"); var _TimeInputUtils = require("./TimeInputUtils"); var _excluded = ["dataHook", "className", "size", "suffix", "prefix", "status", "statusMessage", "invalidMessage", "border", "disabled", "placeholder", "readOnly", "autoSelect", "width", "timeStyle", "onChange", "onInvalid", "step", "noRightBorderRadius", "noLeftBorderRadius", "popoverProps", "onFocus", "onBlur", "excludePastTimes", "filterTime"]; var _jsxFileName = "/home/builduser/work/a9c1ac8876d5057c/packages/wix-style-react/dist/cjs/TimeInput/TimeInput.js"; function _interopRequireWildcard(e, t) { if ("function" == typeof WeakMap) var r = new WeakMap(), n = new WeakMap(); return (_interopRequireWildcard = function _interopRequireWildcard(e, t) { if (!t && e && e.__esModule) return e; var o, i, f = { __proto__: null, default: e }; if (null === e || "object" != typeof e && "function" != typeof e) return f; if (o = t ? n : r) { if (o.has(e)) return o.get(e); o.set(e, f); } for (var _t in e) "default" !== _t && {}.hasOwnProperty.call(e, _t) && ((i = (o = Object.defineProperty) && Object.getOwnPropertyDescriptor(e, _t)) && (i.get || i.set) ? o(f, _t, i) : f[_t] = e[_t]); return f; })(e, t); } function ownKeys(e, r) { var t = Object.keys(e); if (Object.getOwnPropertySymbols) { var o = Object.getOwnPropertySymbols(e); r && (o = o.filter(function (r) { return Object.getOwnPropertyDescriptor(e, r).enumerable; })), t.push.apply(t, o); } return t; } function _objectSpread(e) { for (var r = 1; r < arguments.length; r++) { var t = null != arguments[r] ? arguments[r] : {}; r % 2 ? ownKeys(Object(t), !0).forEach(function (r) { (0, _defineProperty2.default)(e, r, t[r]); }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(e, Object.getOwnPropertyDescriptors(t)) : ownKeys(Object(t)).forEach(function (r) { Object.defineProperty(e, r, Object.getOwnPropertyDescriptor(t, r)); }); } return e; } var DEFAULT_STEP = exports.DEFAULT_STEP = 15; var DEFAULT_TIME_STYLE = exports.DEFAULT_TIME_STYLE = 'short'; var initialState = { isDropdownOpen: false, inputValue: '', selectedId: null, customInputId: null, highlightedOptionId: null, error: false, selectionRange: false, deleteEvent: false, scrollToOption: null, lastSavedDate: null }; var TimeInput = /*#__PURE__*/_react.default.forwardRef((_ref, ref) => { var { dataHook, className, size, suffix, prefix, status, statusMessage, invalidMessage, border, disabled, placeholder, readOnly, autoSelect, width = 'auto', timeStyle = DEFAULT_TIME_STYLE, onChange, onInvalid, step = DEFAULT_STEP, noRightBorderRadius, noLeftBorderRadius, popoverProps, onFocus, onBlur, excludePastTimes, filterTime } = _ref, rest = (0, _objectWithoutProperties2.default)(_ref, _excluded); var inputRef = (0, _react.useRef)(); var context = (0, _react.useContext)(_context.WixStyleReactEnvironmentContext); var locale = rest.locale || context.locale || 'en'; // Resetting seconds so that one of timeSlots would be selected in case value with seconds is passed var value = rest.value ? rest.value.setSeconds(0, 0) : undefined; var timeFilter = (0, _react.useMemo)(() => (0, _TimeInputUtils.getTimeFilter)({ excludePastTimes, filterTime }), [excludePastTimes, filterTime]); var timeSlots = (0, _react.useMemo)(() => (0, _TimeInputUtils.getTimeSlots)({ value, timeStyle, locale, step }).filter(_ref2 => { var { id } = _ref2; return timeFilter(new Date(id)); }), [value, timeStyle, locale, step, timeFilter]); var [state, setState] = _react.default.useReducer((currentState, newState) => _objectSpread(_objectSpread({}, currentState), newState), initialState); var shouldShowError = invalidMessage && state.error; var autofillValue = state.selectionRange ? state.inputValue.substring(state.selectionRange.selectionStart) : null; (0, _react.useEffect)(() => { if (rest.value === null) { setState({ inputValue: '' }); return; } var timeSlot = value ? (0, _TimeInputUtils.getTimeSlot)({ value, timeStyle, locale }) : (0, _TimeInputUtils.getClosestTimeSlot)({ value, timeSlots }); var selectedOption = timeSlot && timeSlots.find(slot => timeSlot.id === slot.id); setState({ inputValue: timeSlot && timeSlot.value, selectedId: selectedOption && selectedOption.id }); }, [value, rest.value, timeStyle, locale, timeSlots]); // select range when suggestedInputValue is changed (0, _react.useEffect)(() => { if (state.selectionRange) { inputRef.current.setSelectionRange(state.selectionRange.selectionStart, state.selectionRange.selectionEnd); } }, [state.selectionRange]); (0, _react.useEffect)(() => { if (state.error) { onInvalid == null || onInvalid({ validationType: state.validationType, value: state.inputValue }); } }, [state.error, onInvalid, state.inputValue, state.validationType]); var openDropdown = (0, _react.useCallback)(() => { if (readOnly) { return; } if (state.selectedId || state.highlightedOptionId) { setState({ isDropdownOpen: true }); return; } var closestTimeSlot = (0, _TimeInputUtils.getClosestTimeSlot)({ value: new Date(state.customInputId || value), timeSlots }); setState({ isDropdownOpen: true, scrollToOption: closestTimeSlot ? closestTimeSlot.id : undefined }); return; }, [state.selectedId, state.highlightedOptionId, state.customInputId, timeSlots, readOnly, value]); (0, _react.useImperativeHandle)(ref, () => ({ focus: () => { inputRef.current.focus(); openDropdown(); }, blur: () => { inputRef.current.blur(); } }), [openDropdown]); var findCurrentOption = (0, _react.useCallback)(() => { if (state.selectedId) { return { id: state.selectedId, value: state.inputValue }; } var highlightedOption = timeSlots.find(slot => slot.id === state.highlightedOptionId); if (highlightedOption) { return highlightedOption; } if (state.inputValue && !state.selectedId) { var customValue = (0, _TimeInputUtils.getCustomTimeSlot)({ value: state.inputValue, timeSlot: timeSlots[0].id, timeStyle, locale }); if (customValue) { return { value: customValue.value, customId: customValue.id }; } } }, [locale, state.highlightedOptionId, state.inputValue, state.selectedId, timeSlots, timeStyle]); var validateAndSetOption = (0, _react.useCallback)(opt => { var _state$lastSavedDate$, _state$lastSavedDate, _date$getTime; var option = opt ? opt : findCurrentOption(); if (!option) { setState({ error: true, validationType: 'formatError' }); return; } else if (!timeFilter(new Date(option.id || option.customId))) { setState({ error: true, validationType: 'outOfBoundsError' }); return; } var date = option ? new Date(option.customId || option.id) : null; var valueChanged = ((_state$lastSavedDate$ = (_state$lastSavedDate = state.lastSavedDate) == null || _state$lastSavedDate.getTime == null ? void 0 : _state$lastSavedDate.getTime()) !== null && _state$lastSavedDate$ !== void 0 ? _state$lastSavedDate$ : null) !== ((_date$getTime = date == null || date.getTime == null ? void 0 : date.getTime()) !== null && _date$getTime !== void 0 ? _date$getTime : null); setState({ selectedId: option.id, customInputId: option.customId, inputValue: option.value, selectionRange: false, isDropdownOpen: false, highlightedOptionId: null, error: false, lastSavedDate: date }); if (option && valueChanged) { onChange && onChange({ date }); } return option; }, [findCurrentOption, timeFilter, onChange, state.lastSavedDate]); var _onKeyDown = (0, _react.useCallback)((e, delegateKeyDown) => { if (e.key === ' ' || e.key === 'Spacebar') { return; } if (!state.isDropdownOpen && e.key === 'ArrowDown') { openDropdown(); return e.preventDefault(); } delegateKeyDown(e); if (e.key === 'Backspace') { setState({ deleteEvent: true }); } if (state.isDropdownOpen) { if (e.key === 'Escape') { setState({ isDropdownOpen: false }); return e.preventDefault(); } // should be handled by error validation if (e.key === 'Enter') { setState({ isDropdownOpen: false }); inputRef.current.setSelectionRange(state.inputValue.length, state.inputValue.length); if (!state.error) { return validateAndSetOption(); } } } }, [state.isDropdownOpen, state.inputValue, openDropdown, validateAndSetOption, state.error]); var onInputChanged = (0, _react.useCallback)(e => { var inputValue = e.target.value; var isDropdownOpen = state.isDropdownOpen; var selectedId = state.selectedId; var selectionRange = false; var error = false; var validationType; // open dropdown when value is selected with keyboard and value is typed again if (!isDropdownOpen) { isDropdownOpen = true; } // validate input if ((0, _TimeInputUtils.isInputInvalid)(e.target.value)) { error = true; validationType = 'formatError'; isDropdownOpen = false; } // unselect dropdown option if input value doesn not match any available timeslot if (inputValue !== timeSlots[state.selectedId]) { selectedId = null; } var suggestedOption = (0, _TimeInputUtils.getSuggestedOption)({ inputValue, timeSlots, locale }); if (!state.deleteEvent && suggestedOption) { var autoFillValue = (0, _TimeInputUtils.getAutoFilledValue)({ inputValue, suggestedOption, locale }); var inputWithAutoFill = inputValue + autoFillValue; inputValue = inputValue + autoFillValue; selectionRange = { selectionStart: inputWithAutoFill.length - autoFillValue.length, selectionEnd: inputWithAutoFill.length }; } var highlightedOptionId = suggestedOption ? suggestedOption.id : null; setState({ isDropdownOpen, error, validationType, selectedId, highlightedOptionId, selectionRange, inputValue, deleteEvent: false }); }, [state.deleteEvent, timeSlots, state.isDropdownOpen, state.selectedId, locale]); var onInputBlur = (0, _react.useCallback)(e => { onBlur && onBlur(e); if (state.isDropdownOpen) { setState({ isDropdownOpen: false }); } if (state.error) { return; } if (state.inputValue === '') { return onChange && onChange({ date: null }); } validateAndSetOption(); }, [validateAndSetOption, onChange, onBlur, state.error, state.isDropdownOpen, state.inputValue]); var getStatusMessage = () => { if (shouldShowError) return invalidMessage; return statusMessage; }; return /*#__PURE__*/_react.default.createElement("div", { className: (0, _TimeInputSt.st)(_TimeInputSt.classes.root, className), style: { width }, "data-hook": dataHook, "data-value": state.selectedId || value, "data-locale": locale, "data-time-style": timeStyle, "data-autofill": autofillValue, "data-scroll-to-option": state.scrollToOption, "data-status-message": statusMessage, __self: void 0, __source: { fileName: _jsxFileName, lineNumber: 414, columnNumber: 7 } }, /*#__PURE__*/_react.default.createElement(_DropdownBase.default, (0, _extends2.default)({ dataHook: _constants.dataHooks.TimeInputDropdown, open: state.isDropdownOpen, onClickOutside: () => setState({ isDropdownOpen: false }), options: timeSlots, onSelect: validateAndSetOption, selectedId: state.selectedId, maxHeight: "216px", markedOptionId: state.highlightedOptionId, focusOnOption: state.highlightedOptionId, focusOnSelectedOption: true, scrollToOption: state.scrollToOption, onMouseDown: e => e.preventDefault(), dynamicWidth: true }, popoverProps, { __self: void 0, __source: { fileName: _jsxFileName, lineNumber: 425, columnNumber: 9 } }), _ref3 => { var { delegateKeyDown } = _ref3; return /*#__PURE__*/_react.default.createElement(_Input.default, { dataHook: _constants.dataHooks.TimeInputInput, size: size, status: shouldShowError ? 'error' : status, statusMessage: getStatusMessage(), suffix: suffix && /*#__PURE__*/_react.default.createElement(_Input.default.Affix, { __self: void 0, __source: { fileName: _jsxFileName, lineNumber: 448, columnNumber: 35 } }, suffix), prefix: prefix && /*#__PURE__*/_react.default.createElement(_Input.default.Affix, { __self: void 0, __source: { fileName: _jsxFileName, lineNumber: 449, columnNumber: 35 } }, prefix), border: border, disabled: disabled, placeholder: placeholder, value: state.inputValue, onChange: onInputChanged, onInputClicked: openDropdown, onKeyDown: e => _onKeyDown(e, delegateKeyDown), ref: inputRef, readOnly: readOnly, autoSelect: autoSelect, noLeftBorderRadius: noLeftBorderRadius, noRightBorderRadius: noRightBorderRadius, onFocus: onFocus, onBlur: onInputBlur, __self: void 0, __source: { fileName: _jsxFileName, lineNumber: 443, columnNumber: 15 } }); })); }); TimeInput.displayName = 'TimeInput'; TimeInput.propTypes = { /** Control the border style of input */ border: _propTypes.default.oneOf(['standard', 'round', 'bottomLine', 'none']), /** Specifies a CSS class name to be appended to the component’s root element */ className: _propTypes.default.string, /** Applies a data-hook HTML attribute that can be used in the tests */ dataHook: _propTypes.default.string, /** Specifies whether component is disabled */ disabled: _propTypes.default.bool, /** Sets locale and formats time according to it */ locale: _propTypes.default.oneOf(_wixDesignSystemsLocaleUtils.SupportedWixLocales), /** Defines a callback function which is called on every time value changes */ // * - return {{ date: Date | null }} onChange: _propTypes.default.func, /** Defines a callback function which is called on cases when invalid time is typed or confirmed with an action. * - return {{ validationType: 'formatError' | 'outOfBoundsError', value: string }} * - `validationType` - type 'formatError'is set when value is in the wrong time format * - type 'outOfBoundsError' is set when `excludePastTimes` or `filterTime` is used and value does not match the filters * - `value` - is set to current input value */ onInvalid: _propTypes.default.func, /** Sets a placeholder message to display */ placeholder: _propTypes.default.string, /** Pass a component you want to show as the prefix of the input, e.g., text, icon */ prefix: _propTypes.default.node, /** Specifies whether input is read only */ readOnly: _propTypes.default.bool, /** Specifies whether input is auto selected on focus */ autoSelect: _propTypes.default.bool, /** Controls the size of the input */ size: _propTypes.default.oneOf(['small', 'medium', 'large']), /** Specify the status of a field */ status: _propTypes.default.oneOf(['error', 'warning', 'loading']), /** Defines the message to display on status icon hover. If not given or empty there will be no tooltip. */ statusMessage: _propTypes.default.node, /** Enables internal validation and defines a message to display when user types invalid time value */ invalidMessage: _propTypes.default.string, /** Specifies the interval between time values shown in dropdown */ step: _propTypes.default.number, /** Pass a component you want to show as the suffix of the input, e.g., text, icon */ suffix: _propTypes.default.node, /** Specifies what time formatting style to use when calling `format()` */ timeStyle: _propTypes.default.string, /** Specifies the current value of the input */ value: _propTypes.default.object, /** Controls the width of the component. `auto` will resize the input to match width of its contents, while `100%` will take up the full parent container width. */ width: _propTypes.default.oneOf(['auto', '100%']), /** Defines a standard input `onFocus` callback */ onFocus: _propTypes.default.func, /** Defines a standard input `onBlur` callback */ onBlur: _propTypes.default.func, /** Specify whether past time slots should be shown or not */ excludePastTimes: _propTypes.default.bool, /** * Specify selectable time slots: * * `param` {Date} `value` - a time to check * * `return` {boolean} - true if `value` should be shown in time slots dropdown, false otherwise */ filterTime: _propTypes.default.func, /** Allows to pass all common popover props. */ popoverProps: _propTypes.default.shape({ appendTo: _propTypes.default.oneOf(['window', 'scrollParent', 'parent', 'viewport']), maxWidth: _propTypes.default.oneOfType([_propTypes.default.string, _propTypes.default.number]), minWidth: _propTypes.default.oneOfType([_propTypes.default.string, _propTypes.default.number]), flip: _propTypes.default.bool, fixed: _propTypes.default.bool, placement: _propTypes.default.oneOf(['auto-start', 'auto', 'auto-end', 'top-start', 'top', 'top-end', 'right-start', 'right', 'right-end', 'bottom-end', 'bottom', 'bottom-start', 'left-end', 'left', 'left-start']), dynamicWidth: _propTypes.default.bool }) }; var _default = exports.default = TimeInput; //# sourceMappingURL=TimeInput.js.map