UNPKG

@wix/design-system

Version:

@wix/design-system

560 lines (553 loc) • 21.7 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 _objectWithoutProperties2 = _interopRequireDefault(require("@babel/runtime/helpers/objectWithoutProperties")); var _defineProperty2 = _interopRequireDefault(require("@babel/runtime/helpers/defineProperty")); 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.js"); var _wixDesignSystemsLocaleUtils = require("wix-design-systems-locale-utils"); var _context = require("../WixStyleReactEnvironmentProvider/context"); var _TimeInputUtils = require("./TimeInputUtils"); var _useEventCallback = require("../common/useEventCallback/useEventCallback"); 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", "disableKeyboardType", "hideStatusSuffix", "value"]; var _jsxFileName = "/home/builduser/work/57e038ea7326c1ec/packages/wix-design-system/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 ERROR_TYPES = { FORMAT: 'formatError', OUT_OF_BOUNDS: 'outOfBoundsError' }; var initialState = { isDropdownOpen: false, inputValue: '', selectedId: null, customInputId: null, highlightedOptionId: null, error: false, selectionRange: false, deleteEvent: false, scrollToOption: null, lastSavedDate: null }; function reducer(state, action) { switch (action.type) { case _constants.ACTION.RESET: return _objectSpread({}, initialState); case _constants.ACTION.SET_STATE: return _objectSpread(_objectSpread({}, state), action.payload); case _constants.ACTION.SET_ERROR: return _objectSpread(_objectSpread({}, state), {}, { error: true, validationType: action.payload }); case _constants.ACTION.OPEN_DROPDOWN: return _objectSpread(_objectSpread({}, state), {}, { isDropdownOpen: true }); case _constants.ACTION.CLOSE_DROPDOWN: return _objectSpread(_objectSpread({}, state), {}, { isDropdownOpen: false }); case _constants.ACTION.DELETE_EVENT: return _objectSpread(_objectSpread({}, state), {}, { deleteEvent: true }); default: return state; } } 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, disableKeyboardType, hideStatusSuffix, value } = _ref, rest = (0, _objectWithoutProperties2.default)(_ref, _excluded); var inputRef = (0, _react.useRef)(); var context = (0, _react.useContext)(_context.WixStyleReactEnvironmentContext); var [state, dispatch] = (0, _react.useReducer)(reducer, initialState); var locale = rest.locale || context.locale || 'en'; var valueAsDate = value ? new Date(value).setSeconds(0, 0) : undefined; var showError = invalidMessage && state.error; var autofillValue = state.selectionRange ? state.inputValue.substring(state.selectionRange.selectionStart) : null; var resolvedStatusMessage = showError ? invalidMessage : statusMessage; var timeFilter = (0, _react.useMemo)(() => (0, _TimeInputUtils.getTimeFilter)({ excludePastTimes, filterTime }), [excludePastTimes, filterTime]); var timeSlots = (0, _react.useMemo)(() => { var slots = (0, _TimeInputUtils.getTimeSlots)({ value: valueAsDate, timeStyle, locale, step }); return slots.filter(_ref2 => { var { id } = _ref2; return timeFilter(new Date(id)); }); }, [valueAsDate, timeStyle, locale, step, timeFilter]); var handleOnInvalid = (0, _useEventCallback.useEventCallback)(() => { onInvalid == null || onInvalid({ validationType: state.validationType, value: state.inputValue }); }); var syncExternalValue = (0, _react.useCallback)(() => { if (value === null) { dispatch({ type: _constants.ACTION.RESET }); return; } var timeSlot = valueAsDate ? (0, _TimeInputUtils.getTimeSlot)({ value: valueAsDate, timeStyle, locale }) : (0, _TimeInputUtils.getClosestTimeSlot)({ value: valueAsDate, timeSlots }); var selectedOption = timeSlot && timeSlots.find(slot => timeSlot.id === slot.id); dispatch({ type: _constants.ACTION.SET_STATE, payload: { inputValue: timeSlot && timeSlot.value, selectedId: selectedOption && selectedOption.id } }); }, [value, valueAsDate, locale, timeSlots, timeStyle]); var openDropdown = (0, _react.useCallback)(() => { if (readOnly) { return; } if (state.selectedId || state.highlightedOptionId) { dispatch({ type: _constants.ACTION.OPEN_DROPDOWN }); return; } var closestTimeSlot = (0, _TimeInputUtils.getClosestTimeSlot)({ value: new Date(state.customInputId || valueAsDate), timeSlots }); dispatch({ type: _constants.ACTION.OPEN_DROPDOWN }); dispatch({ type: _constants.ACTION.SET_STATE, payload: { scrollToOption: closestTimeSlot == null ? void 0 : closestTimeSlot.id } }); return; }, [state.selectedId, state.highlightedOptionId, state.customInputId, timeSlots, readOnly, valueAsDate]); var findSelectedOption = (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 : findSelectedOption(); // Validate if (!option) { dispatch({ type: _constants.ACTION.SET_ERROR, payload: ERROR_TYPES.FORMAT }); return; } else if (!timeFilter(new Date(option.id || option.customId))) { dispatch({ type: _constants.ACTION.SET_ERROR, payload: ERROR_TYPES.OUT_OF_BOUNDS }); return; } // Set selected option var date = option ? new Date(option.customId || option.id) : null; var hasDateChanged = ((_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); dispatch({ type: _constants.ACTION.SET_STATE, payload: { selectedId: option.id, customInputId: option.customId, inputValue: option.value, selectionRange: false, isDropdownOpen: false, highlightedOptionId: null, error: false, lastSavedDate: date } }); if (option && hasDateChanged) { onChange == null || onChange({ date }); } return option; }, [findSelectedOption, 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') { dispatch({ type: _constants.ACTION.DELETE_EVENT }); } if (state.isDropdownOpen) { switch (e.key) { case 'Escape': { dispatch({ type: _constants.ACTION.CLOSE_DROPDOWN }); return e.preventDefault(); } case 'Enter': { dispatch({ type: _constants.ACTION.CLOSE_DROPDOWN }); inputRef.current.setSelectionRange(state.inputValue.length, state.inputValue.length); if (!state.error) { return validateAndSetOption(); } } } } }, [state.isDropdownOpen, state.inputValue, openDropdown, validateAndSetOption, state.error]); var onInputChange = (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/or value is typed again if (!isDropdownOpen) { isDropdownOpen = true; } // Validate current input value if ((0, _TimeInputUtils.isInputInvalid)(e.target.value)) { error = true; validationType = ERROR_TYPES.FORMAT; isDropdownOpen = false; } // Unselect dropdown option if input value does not match any available time slot 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; dispatch({ type: _constants.ACTION.SET_STATE, payload: { isDropdownOpen, error, validationType, selectedId, highlightedOptionId, selectionRange, inputValue, deleteEvent: false } }); }, [state.deleteEvent, timeSlots, state.isDropdownOpen, state.selectedId, locale]); var onInputBlur = (0, _react.useCallback)(e => { onBlur == null || onBlur(e); if (state.isDropdownOpen) { dispatch({ type: _constants.ACTION.CLOSE_DROPDOWN }); } if (state.error) return; if (state.inputValue === '') { return onChange == null ? void 0 : onChange({ date: null }); } validateAndSetOption(); }, [validateAndSetOption, onChange, onBlur, state.error, state.isDropdownOpen, state.inputValue]); (0, _react.useImperativeHandle)(ref, () => ({ focus: () => { inputRef.current.focus(); openDropdown(); }, blur: () => { inputRef.current.blur(); }, clear: () => { dispatch({ type: _constants.ACTION.RESET }); onChange == null || onChange({ date: null }); } }), [onChange, openDropdown]); (0, _react.useEffect)(syncExternalValue, [syncExternalValue]); (0, _react.useEffect)(() => { // Select range when suggestedInputValue is changed if (state.selectionRange) { inputRef.current.setSelectionRange(state.selectionRange.selectionStart, state.selectionRange.selectionEnd); } }, [state.selectionRange]); (0, _react.useEffect)(() => { if (state.error) { handleOnInvalid(); } }, [handleOnInvalid, state.error, state.inputValue, state.validationType]); return /*#__PURE__*/_react.default.createElement("div", { className: (0, _TimeInputSt.st)(_TimeInputSt.classes.root, className), style: { width }, "data-hook": dataHook, "data-value": state.selectedId || valueAsDate, "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: 466, columnNumber: 7 } }, /*#__PURE__*/_react.default.createElement(_DropdownBase.default, (0, _extends2.default)({ dataHook: _constants.DATA_HOOK.TimeInputDropdown, open: state.isDropdownOpen, onClickOutside: () => dispatch({ type: _constants.ACTION.CLOSE_DROPDOWN }), 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: 477, columnNumber: 9 } }), _ref3 => { var { isOpen, activeDescendantId, listboxId, delegateKeyDown } = _ref3; return /*#__PURE__*/_react.default.createElement(_Input.default, { dataHook: _constants.DATA_HOOK.TimeInputInput, size: size, status: showError ? 'error' : status, statusMessage: resolvedStatusMessage, suffix: suffix && /*#__PURE__*/_react.default.createElement(_Input.default.Affix, { __self: void 0, __source: { fileName: _jsxFileName, lineNumber: 500, columnNumber: 35 } }, suffix), prefix: prefix && /*#__PURE__*/_react.default.createElement(_Input.default.Affix, { __self: void 0, __source: { fileName: _jsxFileName, lineNumber: 501, columnNumber: 35 } }, prefix), border: border, disabled: disabled, placeholder: placeholder, value: state.inputValue, onChange: onInputChange, onInputClicked: openDropdown, onKeyDown: e => _onKeyDown(e, delegateKeyDown), ref: inputRef, readOnly: readOnly, autoSelect: autoSelect, noLeftBorderRadius: noLeftBorderRadius, noRightBorderRadius: noRightBorderRadius, onFocus: onFocus, onBlur: onInputBlur, disableEditing: disableKeyboardType, hideStatusSuffix: hideStatusSuffix, ariaAutocomplete: "list", ariaExpanded: isOpen, ariaControls: isOpen ? listboxId : undefined, ariaActiveDescendant: isOpen ? activeDescendantId : undefined, __self: void 0, __source: { fileName: _jsxFileName, lineNumber: 495, 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. * @internal */ 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, /** Specifies whether status suffix is hidden. */ hideStatusSuffix: _propTypes.default.bool, /** 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