@wix/design-system
Version:
@wix/design-system
560 lines (553 loc) • 21.7 kB
JavaScript
"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