grommet
Version:
focus on the essential experience
391 lines (381 loc) • 16.6 kB
JavaScript
;
exports.__esModule = true;
exports.DateInput = void 0;
var _react = _interopRequireWildcard(require("react"));
var _styledComponents = _interopRequireDefault(require("styled-components"));
var _Calendar = require("grommet-icons/icons/Calendar");
var _AnnounceContext = require("../../contexts/AnnounceContext");
var _MessageContext = require("../../contexts/MessageContext");
var _Box = require("../Box");
var _Button = require("../Button");
var _Calendar2 = require("../Calendar");
var _Drop = require("../Drop");
var _DropButton = require("../DropButton");
var _Form = require("../Form");
var _Keyboard = require("../Keyboard");
var _MaskedInput = require("../MaskedInput");
var _utils = require("../../utils");
var _readOnly = require("../../utils/readOnly");
var _utils2 = require("./utils");
var _propTypes = require("./propTypes");
var _Calendar3 = require("../Calendar/Calendar");
var _CopyButton = require("../TextInput/CopyButton");
var _useThemeValue2 = require("../../utils/useThemeValue");
var _excluded = ["buttonProps", "calendarProps", "defaultValue", "disabled", "dropProps", "format", "id", "icon", "inline", "inputProps", "name", "onChange", "onFocus", "plain", "readOnly", "readOnlyCopy", "reverse", "value", "messages"],
_excluded2 = ["icon"];
function _interopRequireDefault(e) { return e && e.__esModule ? e : { "default": e }; }
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 _extends() { return _extends = Object.assign ? Object.assign.bind() : function (n) { for (var e = 1; e < arguments.length; e++) { var t = arguments[e]; for (var r in t) ({}).hasOwnProperty.call(t, r) && (n[r] = t[r]); } return n; }, _extends.apply(null, arguments); }
function _objectWithoutPropertiesLoose(r, e) { if (null == r) return {}; var t = {}; for (var n in r) if ({}.hasOwnProperty.call(r, n)) { if (-1 !== e.indexOf(n)) continue; t[n] = r[n]; } return t; }
var StyledDateInputContainer = (0, _styledComponents["default"])(_Box.Box).withConfig({
// to not pass props on dom through Box
shouldForwardProp: function shouldForwardProp(prop) {
return prop !== 'disabled';
}
}).withConfig({
displayName: "DateInput__StyledDateInputContainer",
componentId: "sc-1jfta23-0"
})(["", " ", "}"], function (props) {
return props.disabled && (0, _utils.disabledStyle)();
}, function (props) {
return props.readOnlyProp && (0, _readOnly.readOnlyStyle)(props.theme);
});
var getReference = function getReference(value) {
var adjustedDate;
var res;
if (typeof value === 'string') res = value;else if (Array.isArray(value) && Array.isArray(value[0])) res = value[0].find(function (date) {
return date;
});else if (Array.isArray(value) && value.length) {
res = value[0];
}
if (res) {
adjustedDate = (0, _utils.setHoursWithOffset)(res);
}
return adjustedDate;
};
var DateInput = exports.DateInput = /*#__PURE__*/(0, _react.forwardRef)(function (_ref, refArg) {
var _theme$icon, _theme$dateInput$icon, _theme$dateInput$icon2, _theme$dateInput$butt, _theme$dateInput$butt2;
var buttonProps = _ref.buttonProps,
calendarProps = _ref.calendarProps,
defaultValue = _ref.defaultValue,
disabled = _ref.disabled,
dropProps = _ref.dropProps,
format = _ref.format,
id = _ref.id,
icon = _ref.icon,
_ref$inline = _ref.inline,
inline = _ref$inline === void 0 ? false : _ref$inline,
inputProps = _ref.inputProps,
name = _ref.name,
_onChange = _ref.onChange,
_onFocus = _ref.onFocus,
plain = _ref.plain,
readOnlyProp = _ref.readOnly,
readOnlyCopy = _ref.readOnlyCopy,
_ref$reverse = _ref.reverse,
reverseProp = _ref$reverse === void 0 ? false : _ref$reverse,
valueArg = _ref.value,
messages = _ref.messages,
rest = _objectWithoutPropertiesLoose(_ref, _excluded);
var _useThemeValue = (0, _useThemeValue2.useThemeValue)(),
theme = _useThemeValue.theme,
passThemeFlag = _useThemeValue.passThemeFlag;
var announce = (0, _react.useContext)(_AnnounceContext.AnnounceContext);
var _useContext = (0, _react.useContext)(_MessageContext.MessageContext),
formatMessage = _useContext.format;
var iconSize = ((_theme$icon = theme.icon) == null ? void 0 : _theme$icon.matchSize) && rest.size || ((_theme$dateInput$icon = theme.dateInput.icon) == null ? void 0 : _theme$dateInput$icon.size);
var _useContext2 = (0, _react.useContext)(_Form.FormContext),
useFormInput = _useContext2.useFormInput;
var ref = (0, _utils.useForwardedRef)(refArg);
var containerRef = (0, _react.useRef)();
var readOnly = readOnlyProp || readOnlyCopy;
var _useFormInput = useFormInput({
name: name,
value: valueArg,
initialValue: defaultValue
}),
value = _useFormInput[0],
setValue = _useFormInput[1];
var usingKeyboard = (0, _utils.useKeyboard)();
var CalendarIcon = ((_theme$dateInput$icon2 = theme.dateInput.icon) == null ? void 0 : _theme$dateInput$icon2.calendar) || _Calendar.Calendar;
var _useState = (0, _react.useState)((0, _Calendar3.getOutputFormat)(value)),
outputFormat = _useState[0],
setOutputFormat = _useState[1];
(0, _react.useEffect)(function () {
setOutputFormat(function (previousFormat) {
var nextFormat = (0, _Calendar3.getOutputFormat)(value);
// when user types, date could become something like 07//2020
// and value becomes undefined. don't lose the format from the
// previous valid date
return previousFormat !== nextFormat ? previousFormat : nextFormat;
});
}, [value]);
// keep track of timestamp from original date(s)
var _useState2 = (0, _react.useState)(getReference(value)),
reference = _useState2[0],
setReference = _useState2[1];
// do we expect multiple dates?
var range = Array.isArray(value) || format && format.includes('-');
// parse format and build a formal schema we can use elsewhere
var schema = (0, _react.useMemo)(function () {
return (0, _utils2.formatToSchema)(format);
}, [format]);
// mask is only used when a format is provided
var mask = (0, _react.useMemo)(function () {
return (0, _utils2.schemaToMask)(schema);
}, [schema]);
// textValue is only used when a format is provided
var _useState3 = (0, _react.useState)(schema ? (0, _utils2.valueToText)(value, schema) : undefined),
textValue = _useState3[0],
setTextValue = _useState3[1];
var readOnlyCopyValidation = formatMessage({
id: 'input.readOnlyCopy.validation',
messages: messages
});
var readOnlyCopyPrompt = formatMessage({
id: 'input.readOnlyCopy.prompt',
messages: messages
});
var _useState4 = (0, _react.useState)(readOnlyCopyPrompt),
tip = _useState4[0],
setTip = _useState4[1];
// Setting the icon through `inputProps` is deprecated.
// The `icon` prop should be used instead.
var _ref2 = inputProps || {},
MaskedInputIcon = _ref2.icon,
restOfInputProps = _objectWithoutPropertiesLoose(_ref2, _excluded2);
if (MaskedInputIcon) {
console.warn("Customizing the DateInput icon through inputProps is deprecated.\nUse the icon prop instead.");
}
var reverse = reverseProp || restOfInputProps.reverse;
var calendarDropdownAlign = {
top: 'bottom',
left: 'left'
};
// We need to distinguish between the caller changing a Form value
// and the user typing a date that he isn't finished with yet.
// To handle this, we see if we have a value and the text value
// associated with it doesn't align to it, then we update the text value.
// We compare using textToValue to avoid "06/01/2021" not
// matching "06/1/2021".
(0, _react.useEffect)(function () {
if (schema && value !== undefined) {
var nextTextValue = (0, _utils2.valueToText)(value, schema);
if (!(0, _utils2.valuesAreEqual)((0, _utils2.textToValue)(textValue, schema, range, reference), (0, _utils2.textToValue)(nextTextValue, schema, range, reference)) || textValue === '' && nextTextValue !== '') {
setTextValue(nextTextValue);
}
}
}, [range, schema, textValue, reference, value]);
// textValue of MaskedInput is controlled.
// for uncontrolled forms, ensure the reset event
// resets the textValue
(0, _react.useEffect)(function () {
var _ref$current;
var form = ref == null || (_ref$current = ref.current) == null ? void 0 : _ref$current.form;
var handleFormReset = function handleFormReset(e) {
if (schema && ref.current && e.target.contains(ref.current)) {
setTextValue('');
}
};
// place the listener on the form directly. if listener is on window,
// the event could get blocked if caller has e.stopPropagation(), etc. in
// their form onReset
form == null || form.addEventListener('reset', handleFormReset);
return function () {
return form == null ? void 0 : form.removeEventListener('reset', handleFormReset);
};
}, [schema, ref]);
// when format and not inline, whether to show the Calendar in a Drop
var _useState5 = (0, _react.useState)(),
open = _useState5[0],
setOpen = _useState5[1];
var openCalendar = (0, _react.useCallback)(function () {
setOpen(true);
announce(formatMessage({
id: 'dateInput.enterCalendar',
messages: messages
}));
}, [announce, formatMessage, messages]);
var closeCalendar = (0, _react.useCallback)(function () {
if (usingKeyboard && !inline && ref != null && ref.current) {
setTimeout(function () {
var _ref$current2;
ref == null || (_ref$current2 = ref.current) == null || _ref$current2.focus();
}, 0);
}
setOpen(false);
announce(formatMessage({
id: 'dateInput.exitCalendar',
messages: messages
}));
}, [announce, formatMessage, messages, usingKeyboard, ref, inline]);
var dates = (0, _react.useMemo)(function () {
return range && value != null && value.length ? [value] : undefined;
}, [range, value]);
var calendar = /*#__PURE__*/_react["default"].createElement(_Calendar2.Calendar, _extends({
ref: inline ? ref : undefined,
id: inline && !format ? id : undefined,
range: range,
date: range ? undefined : value
// when caller initializes with empty array, dates should be undefined
// allowing the user to select both begin and end of the range
,
dates: dates
// places focus on days grid when Calendar opens
,
initialFocus: open ? 'days' : undefined,
onSelect: disabled ? undefined : function (nextValue) {
var normalizedValue;
if (range && Array.isArray(nextValue)) {
normalizedValue = nextValue[0];
} // clicking an edge date removes it
else if (range && nextValue) normalizedValue = [nextValue, nextValue];else normalizedValue = nextValue;
if (schema) setTextValue((0, _utils2.valueToText)(normalizedValue, schema));
setValue(normalizedValue);
setReference(getReference(nextValue));
if (_onChange) _onChange({
value: normalizedValue
});
if (open && !range) {
closeCalendar();
}
}
}, calendarProps));
var formContextValue = (0, _react.useMemo)(function () {
return {
useFormInput: function useFormInput(_ref3) {
var valueProp = _ref3.value;
return [valueProp, function () {}];
}
};
}, []);
if (!format) {
// When no format is specified, we don't give the user a way to type
if (inline) return calendar;
return /*#__PURE__*/_react["default"].createElement(_DropButton.DropButton, _extends({
ref: ref,
id: id,
dropProps: _extends({
align: calendarDropdownAlign
}, dropProps),
dropContent: calendar,
icon: icon || MaskedInputIcon || /*#__PURE__*/_react["default"].createElement(CalendarIcon, {
size: iconSize
})
}, buttonProps));
}
var onClickCopy = function onClickCopy() {
global.navigator.clipboard.writeText(textValue);
announce(readOnlyCopyValidation, 'assertive');
setTip(readOnlyCopyValidation);
};
var onBlurCopy = function onBlurCopy() {
if (tip === readOnlyCopyValidation) setTip(readOnlyCopyPrompt);
};
var DateInputButton = readOnlyCopy ? /*#__PURE__*/_react["default"].createElement(_CopyButton.CopyButton, {
disabled: disabled,
onBlurCopy: onBlurCopy,
onClickCopy: onClickCopy,
readOnlyCopyPrompt: readOnlyCopyPrompt,
tip: tip,
value: value
}) : /*#__PURE__*/_react["default"].createElement(_Button.Button, {
onClick: open ? closeCalendar : openCalendar,
disabled: disabled,
plain: true,
icon: icon || MaskedInputIcon || /*#__PURE__*/_react["default"].createElement(CalendarIcon, {
size: iconSize
}),
margin: reverse ? {
left: (_theme$dateInput$butt = theme.dateInput.button) == null ? void 0 : _theme$dateInput$butt.margin
} : {
right: (_theme$dateInput$butt2 = theme.dateInput.button) == null ? void 0 : _theme$dateInput$butt2.margin
}
});
var input = /*#__PURE__*/_react["default"].createElement(_Form.FormContext.Provider, {
key: "input"
// don't let MaskedInput drive the Form
,
value: formContextValue
}, /*#__PURE__*/_react["default"].createElement(_Keyboard.Keyboard, {
onEsc: open ? function () {
return closeCalendar();
} : undefined,
onSpace: function onSpace(event) {
if (!readOnlyCopy) {
event.preventDefault();
if (!readOnly) openCalendar();
}
}
}, /*#__PURE__*/_react["default"].createElement(StyledDateInputContainer, _extends({
ref: containerRef,
border: !plain,
round: theme.dateInput.container.round,
direction: "row",
disabled: disabled
// readOnly prop shouldn't get passed to the dom here
,
readOnlyProp: readOnly,
fill: true
}, passThemeFlag), reverse && (!readOnly || readOnlyCopy) && DateInputButton, /*#__PURE__*/_react["default"].createElement(_MaskedInput.MaskedInput, _extends({
readOnly: readOnly,
ref: ref,
id: id,
name: name,
reverse: true,
disabled: disabled,
mask: mask,
plain: true
}, restOfInputProps, rest, {
value: textValue,
onChange: function onChange(event) {
var nextTextValue = event.target.value;
setTextValue(nextTextValue);
var nextValue = (0, _utils2.textToValue)(nextTextValue, schema, range, reference, outputFormat);
var validatedNextValue = (0, _utils2.validateBounds)(calendarProps == null ? void 0 : calendarProps.bounds, nextValue);
if (!validatedNextValue && nextValue) {
setTextValue('');
}
if (validatedNextValue !== undefined) setReference(getReference(validatedNextValue));
// update value even when undefined
setValue(validatedNextValue);
if (_onChange) {
event.persist(); // extract from React synthetic event pool
var adjustedEvent = event;
adjustedEvent.value = validatedNextValue;
_onChange(adjustedEvent);
}
},
onFocus: function onFocus(event) {
if (!readOnly) {
announce(formatMessage({
id: 'dateInput.openCalendar',
messages: messages
}));
}
if (_onFocus) _onFocus(event);
}
})), !reverse && (!readOnly || readOnlyCopy) && DateInputButton)));
if (inline) {
return /*#__PURE__*/_react["default"].createElement(_Box.Box, null, input, calendar);
}
if (open && !readOnly) {
return [input, /*#__PURE__*/_react["default"].createElement(_Drop.Drop, _extends({
key: "drop",
overflow: "visible",
id: id ? id + "__drop" : undefined,
target: containerRef.current,
align: _extends({}, calendarDropdownAlign, dropProps),
onEsc: closeCalendar,
onClickOutside: function onClickOutside(_ref4) {
var target = _ref4.target;
if (target !== containerRef.current && !containerRef.current.contains(target)) {
closeCalendar();
}
}
}, dropProps), calendar)];
}
return input;
});
DateInput.displayName = 'DateInput';
DateInput.propTypes = _propTypes.DateInputPropTypes;