UNPKG

grommet

Version:

focus on the essential experience

386 lines (377 loc) 15.2 kB
var _excluded = ["buttonProps", "calendarProps", "defaultValue", "disabled", "dropProps", "format", "id", "icon", "inline", "inputProps", "name", "onChange", "onFocus", "plain", "readOnly", "readOnlyCopy", "reverse", "value", "messages"], _excluded2 = ["icon"]; 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; } import React, { useRef, forwardRef, useContext, useEffect, useMemo, useState, useCallback } from 'react'; import styled from 'styled-components'; import { Calendar as GrommetCalendarIcon } from 'grommet-icons/icons/Calendar'; import { AnnounceContext } from '../../contexts/AnnounceContext'; import { MessageContext } from '../../contexts/MessageContext'; import { Box } from '../Box'; import { Button } from '../Button'; import { Calendar } from '../Calendar'; import { Drop } from '../Drop'; import { DropButton } from '../DropButton'; import { FormContext } from '../Form'; import { Keyboard } from '../Keyboard'; import { MaskedInput } from '../MaskedInput'; import { useForwardedRef, setHoursWithOffset, disabledStyle, useKeyboard } from '../../utils'; import { readOnlyStyle } from '../../utils/readOnly'; import { formatToSchema, schemaToMask, valuesAreEqual, valueToText, textToValue, validateBounds } from './utils'; import { DateInputPropTypes } from './propTypes'; import { getOutputFormat } from '../Calendar/Calendar'; import { CopyButton } from '../TextInput/CopyButton'; import { useThemeValue } from '../../utils/useThemeValue'; var StyledDateInputContainer = styled(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 && disabledStyle(); }, function (props) { return props.readOnlyProp && 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 = setHoursWithOffset(res); } return adjustedDate; }; var DateInput = /*#__PURE__*/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 = useThemeValue(), theme = _useThemeValue.theme, passThemeFlag = _useThemeValue.passThemeFlag; var announce = useContext(AnnounceContext); var _useContext = useContext(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 = useContext(FormContext), useFormInput = _useContext2.useFormInput; var ref = useForwardedRef(refArg); var containerRef = useRef(); var readOnly = readOnlyProp || readOnlyCopy; var _useFormInput = useFormInput({ name: name, value: valueArg, initialValue: defaultValue }), value = _useFormInput[0], setValue = _useFormInput[1]; var usingKeyboard = useKeyboard(); var CalendarIcon = ((_theme$dateInput$icon2 = theme.dateInput.icon) == null ? void 0 : _theme$dateInput$icon2.calendar) || GrommetCalendarIcon; var _useState = useState(getOutputFormat(value)), outputFormat = _useState[0], setOutputFormat = _useState[1]; useEffect(function () { setOutputFormat(function (previousFormat) { var nextFormat = 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 = 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 = useMemo(function () { return formatToSchema(format); }, [format]); // mask is only used when a format is provided var mask = useMemo(function () { return schemaToMask(schema); }, [schema]); // textValue is only used when a format is provided var _useState3 = useState(schema ? 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 = 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". useEffect(function () { if (schema && value !== undefined) { var nextTextValue = valueToText(value, schema); if (!valuesAreEqual(textToValue(textValue, schema, range, reference), 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 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 = useState(), open = _useState5[0], setOpen = _useState5[1]; var openCalendar = useCallback(function () { setOpen(true); announce(formatMessage({ id: 'dateInput.enterCalendar', messages: messages })); }, [announce, formatMessage, messages]); var closeCalendar = 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 = useMemo(function () { return range && value != null && value.length ? [value] : undefined; }, [range, value]); var calendar = /*#__PURE__*/React.createElement(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(valueToText(normalizedValue, schema)); setValue(normalizedValue); setReference(getReference(nextValue)); if (_onChange) _onChange({ value: normalizedValue }); if (open && !range) { closeCalendar(); } } }, calendarProps)); var formContextValue = 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.createElement(DropButton, _extends({ ref: ref, id: id, dropProps: _extends({ align: calendarDropdownAlign }, dropProps), dropContent: calendar, icon: icon || MaskedInputIcon || /*#__PURE__*/React.createElement(CalendarIcon, { size: iconSize }) }, buttonProps)); } var onClickCopy = function onClickCopy() { navigator.clipboard.writeText(textValue); announce(readOnlyCopyValidation, 'assertive'); setTip(readOnlyCopyValidation); }; var onBlurCopy = function onBlurCopy() { if (tip === readOnlyCopyValidation) setTip(readOnlyCopyPrompt); }; var DateInputButton = readOnlyCopy ? /*#__PURE__*/React.createElement(CopyButton, { disabled: disabled, onBlurCopy: onBlurCopy, onClickCopy: onClickCopy, readOnlyCopyPrompt: readOnlyCopyPrompt, tip: tip, value: value }) : /*#__PURE__*/React.createElement(Button, { onClick: open ? closeCalendar : openCalendar, disabled: disabled, plain: true, icon: icon || MaskedInputIcon || /*#__PURE__*/React.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.createElement(FormContext.Provider, { key: "input" // don't let MaskedInput drive the Form , value: formContextValue }, /*#__PURE__*/React.createElement(Keyboard, { onEsc: open ? function () { return closeCalendar(); } : undefined, onSpace: function onSpace(event) { if (!readOnlyCopy) { event.preventDefault(); if (!readOnly) openCalendar(); } } }, /*#__PURE__*/React.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.createElement(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 = textToValue(nextTextValue, schema, range, reference, outputFormat); var validatedNextValue = 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.createElement(Box, null, input, calendar); } if (open && !readOnly) { return [input, /*#__PURE__*/React.createElement(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 = DateInputPropTypes; export { DateInput };