UNPKG

@razorpay/blade

Version:

The Design System that powers Razorpay

395 lines (379 loc) 19.5 kB
import _typeof from '@babel/runtime/helpers/typeof'; import _defineProperty from '@babel/runtime/helpers/defineProperty'; import _slicedToArray from '@babel/runtime/helpers/slicedToArray'; import _objectWithoutProperties from '@babel/runtime/helpers/objectWithoutProperties'; import React__default from 'react'; import { useDatesContext } from '@mantine/dates'; import { stripDelimiters, validateAndParseDateInput, rangeFormattedValue, getTextInputFormat, finalInputFormat, getFormattedDate, rangeInputPlaceHolder } from './utils.js'; import { useDatePickerContext } from './DatePickerContext.js'; import '../Box/BaseBox/index.js'; import '../Input/TextInput/index.js'; import '../../utils/index.js'; import '../Icons/index.js'; import '../../utils/makeAnalyticsAttribute/index.js'; import { jsx, Fragment, jsxs } from 'react/jsx-runtime'; import CalendarIcon from '../Icons/CalendarIcon/CalendarIcon.js'; import { TextInput } from '../Input/TextInput/TextInput.js'; import { isReactNative } from '../../utils/platform/isReactNative.js'; import { makeAnalyticsAttribute } from '../../utils/makeAnalyticsAttribute/makeAnalyticsAttribute.js'; import { BaseBox } from '../Box/BaseBox/BaseBox.web.js'; var _excluded = ["format", "date", "setControlledValue", "effectiveSelectionType", "leadingDropdown", "tags", "id", "selectedPresetLabel", "showClearButton", "onClearButtonClick"], _excluded2 = ["value", "name", "isRequired", "isDisabled"], _excluded3 = ["selectionType", "referenceProps", "inputRef", "date", "label", "labelPosition", "labelSuffix", "labelTrailing", "autoFocus", "name", "size", "necessityIndicator", "successText", "errorText", "helpText", "format", "placeholder", "setControlledValue", "leadingDropdown", "selectedPreset", "excludeDate", "minDate", "maxDate", "effectiveSelectionType", "showClearButton", "onClearButtonClick", "selectedPresetLabel"]; 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) { _defineProperty(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 _DateInput = function _DateInput(props, ref) { var _datePickerContext$is, _datePickerContext$di; var format = props.format, date = props.date, setControlledValue = props.setControlledValue, effectiveSelectionType = props.effectiveSelectionType, leadingDropdown = props.leadingDropdown, tags = props.tags, id = props.id, selectedPresetLabel = props.selectedPresetLabel, showClearButton = props.showClearButton, onClearButtonClick = props.onClearButtonClick, textInputProps = _objectWithoutProperties(props, _excluded); // Use context to check datepicker state - more reliable than prop drilling var datePickerContext = useDatePickerContext(); var isPopupOpen = (_datePickerContext$is = datePickerContext === null || datePickerContext === void 0 ? void 0 : datePickerContext.isDatePickerBodyOpen) !== null && _datePickerContext$is !== void 0 ? _datePickerContext$is : false; var displayFormat = (_datePickerContext$di = datePickerContext === null || datePickerContext === void 0 ? void 0 : datePickerContext.displayFormat) !== null && _datePickerContext$di !== void 0 ? _datePickerContext$di : 'default'; var isCompactMode = displayFormat === 'compact'; var _React$useState = React__default.useState(['']), _React$useState2 = _slicedToArray(_React$useState, 2), inputValue = _React$useState2[0], setInputValue = _React$useState2[1]; var _React$useState3 = React__default.useState(undefined), _React$useState4 = _slicedToArray(_React$useState3, 2), validationError = _React$useState4[0], setValidationError = _React$useState4[1]; var _React$useState5 = React__default.useState(false), _React$useState6 = _slicedToArray(_React$useState5, 2), isFocused = _React$useState6[0], setIsFocused = _React$useState6[1]; var shouldShowCalendarIcon = !Boolean(leadingDropdown); // Determine selection type: prefer preset context calculation over props // This handles "Today" presets that should display as single even though data is range var isRange = effectiveSelectionType === 'single' ? false : effectiveSelectionType === 'range' || props.selectionType === 'range'; // Sync internal input state with external formatted values from parent component // textInputProps.value comes from DatePickerInput as formatted strings: ["25/12/2024", "31/12/2024"] // We strip delimiters for internal processing: ["25122024", "31122024"] // This prevents double formatting and helps to validate the input easier during user typing React__default.useEffect(function () { if (textInputProps.value) { setInputValue(isRange ? [stripDelimiters(textInputProps.value[0]), stripDelimiters(textInputProps.value[1])] : [stripDelimiters(textInputProps.value[0])]); } }, [textInputProps.value, isRange, format]); // Clear validation error only when the actual selected date changes // (e.g., user selected a valid date from the calendar). This avoids // clearing errors during typing/blur unless the value truly updated. React__default.useEffect(function () { setValidationError(undefined); }, [date]); var applyDateValue = React__default.useCallback(function (inputValue) { var shouldClearWhenEmpty = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : false; if (inputValue !== null && inputValue !== void 0 && inputValue.trim()) { // Validate input and get parsed dates in one atomic operation (includes all constraints) var validation = validateAndParseDateInput(inputValue, isRange, format, { excludeDate: props.excludeDate, minDate: props.minDate, maxDate: props.maxDate }); if (validation.shouldBlock) { return; // Block invalid input to prevent data corruption } // Apply the pre-parsed date values to controlled state (no redundant parsing) if (validation.parsedValue !== undefined) { var finalValue = validation.parsedValue; // Special handling: if preset context shows single but props expect range // (like "Today" preset), convert single date back to same-day range if (effectiveSelectionType === 'single' && props.selectionType === 'range' && validation.parsedValue instanceof Date) { finalValue = [validation.parsedValue, validation.parsedValue]; } setControlledValue === null || setControlledValue === void 0 || setControlledValue(finalValue); } } else if (shouldClearWhenEmpty) { // Clear controlled value when input is emptied (onChange only, not onBlur) setControlledValue === null || setControlledValue === void 0 || setControlledValue(isRange ? [null, null] : null); } }, [isRange, setControlledValue, effectiveSelectionType, props.selectionType, format, props.excludeDate, props.minDate, props.maxDate]); var handleInputChange = function handleInputChange(_ref) { var value = _ref.value; var inputValue = value !== null && value !== void 0 ? value : ''; setValidationError(undefined); if (inputValue !== null && inputValue !== void 0 && inputValue.trim()) { var validation = validateAndParseDateInput(inputValue, isRange, format, { excludeDate: props.excludeDate, minDate: props.minDate, maxDate: props.maxDate }); if (validation.shouldBlock && validation.error) { setValidationError(validation.error); } } // Apply changes immediately during typing (with empty clearing enabled) applyDateValue(inputValue, true); }; var handleBlur = React__default.useCallback(function (params) { var _ref2, _params$event$target$, _params$event; var currentInputValue = (_ref2 = (_params$event$target$ = (_params$event = params.event) === null || _params$event === void 0 ? void 0 : _params$event.target.value) !== null && _params$event$target$ !== void 0 ? _params$event$target$ : params.value) !== null && _ref2 !== void 0 ? _ref2 : ''; setValidationError(undefined); if (currentInputValue !== null && currentInputValue !== void 0 && currentInputValue.trim()) { // Validate complete input and show errors to user on blur (includes all constraints) var validation = validateAndParseDateInput(currentInputValue, isRange, format, { excludeDate: props.excludeDate, minDate: props.minDate, maxDate: props.maxDate }); if (validation.shouldBlock && validation.error) { setValidationError(validation.error); return; // Don't apply invalid values } } // Apply final value on blur (without empty clearing to preserve existing dates) applyDateValue(currentInputValue, false); }, [applyDateValue, isRange]); var handleFocus = React__default.useCallback(function () { setIsFocused(true); }, []); var handleBlurWithFocusState = React__default.useCallback(function (params) { setIsFocused(false); handleBlur(params); }, [handleBlur]); // Compute input props based on whether we're showing a preset label or date input // DatePicker is "active" when input is focused OR popup is open (from context) // This ensures consistent behavior when focus moves between input and calendar var isDatePickerActive = isFocused || isPopupOpen; // Check if there's actually a valid date selection. in range mode we need to check if both dates are not null var hasValidSelection = isRange ? (date === null || date === void 0 ? void 0 : date[0]) && (date === null || date === void 0 ? void 0 : date[1]) : Boolean(date); // Show preset label only when: // 1. displayFormat is 'compact' // 2. There's a preset label to show // 3. There's actually a valid date selection (not cleared) // 4. DatePicker is NOT active (closed and not focused) var showPresetLabel = displayFormat === 'compact' && selectedPresetLabel && hasValidSelection && !isDatePickerActive; var getInputDisplayProps = function getInputDisplayProps() { var _textInputProps$error; if (showPresetLabel) { // Preset label mode: show clean input with just the label (not focused) return { type: 'text', value: selectedPresetLabel, leadingIcon: CalendarIcon, format: undefined, validationState: textInputProps.validationState, errorText: textInputProps.errorText, onChange: handleInputChange, onBlur: handleBlurWithFocusState, onFocus: handleFocus }; } // Date input mode: show formatted date with full editing capabilities var dateDisplayValue = isRange ? rangeFormattedValue(inputValue[0], inputValue[1]) : inputValue[0]; var dateInputFormat = isRange ? getTextInputFormat(finalInputFormat(inputValue[0], inputValue[1], format), true) : getTextInputFormat(format, false); // In compact mode: always show icon instead of dropdown (presets accessed via popup sidebar) // In non-compact mode: show the dropdown as normal // Note: We must conditionally include `leading` only when defined, otherwise `leading: undefined` // would override/hide `leadingIcon` in the TextInput component var showLeadingDropdown = isCompactMode ? undefined : leadingDropdown; var showLeadingIcon = isCompactMode || shouldShowCalendarIcon ? CalendarIcon : undefined; return _objectSpread(_objectSpread({ type: 'number', value: dateDisplayValue, leadingIcon: showLeadingIcon }, showLeadingDropdown ? { leading: showLeadingDropdown } : {}), {}, { format: dateInputFormat, validationState: validationError ? 'error' : textInputProps.validationState, errorText: (_textInputProps$error = textInputProps.errorText) !== null && _textInputProps$error !== void 0 ? _textInputProps$error : validationError, onChange: handleInputChange, onBlur: handleBlurWithFocusState, onFocus: handleFocus }); }; return /*#__PURE__*/jsx(TextInput, _objectSpread(_objectSpread(_objectSpread({}, textInputProps), {}, { ref: ref }, getInputDisplayProps()), {}, { onClick: function onClick(e) { var _textInputProps$onCli; if (textInputProps.isDisabled) { return; } (_textInputProps$onCli = textInputProps.onClick) === null || _textInputProps$onCli === void 0 || _textInputProps$onCli.call(textInputProps, e); }, onKeyDown: function onKeyDown(_ref3) { var _textInputProps$onKey; var event = _ref3.event; // @ts-expect-error (_textInputProps$onKey = textInputProps.onKeyDown) === null || _textInputProps$onKey === void 0 || _textInputProps$onKey.call(textInputProps, event); }, showClearButton: showClearButton, onClearButtonClick: onClearButtonClick })); }; var DateInput = /*#__PURE__*/React__default.forwardRef(_DateInput); var HiddenInput = function HiddenInput(_ref4) { var value = _ref4.value, name = _ref4.name, isRequired = _ref4.isRequired, isDisabled = _ref4.isDisabled, rest = _objectWithoutProperties(_ref4, _excluded2); if (isReactNative()) return /*#__PURE__*/jsx(Fragment, {}); return /*#__PURE__*/jsx("input", _objectSpread({ hidden: true, name: name, value: value, required: isRequired, disabled: isDisabled, readOnly: true }, makeAnalyticsAttribute(rest))); }; var _DatePickerInput = function _DatePickerInput(_ref5, ref) { var selectionType = _ref5.selectionType, referenceProps = _ref5.referenceProps, inputRef = _ref5.inputRef, date = _ref5.date, label = _ref5.label, labelPosition = _ref5.labelPosition, labelSuffix = _ref5.labelSuffix, labelTrailing = _ref5.labelTrailing, autoFocus = _ref5.autoFocus, name = _ref5.name, _ref5$size = _ref5.size, size = _ref5$size === void 0 ? 'medium' : _ref5$size, necessityIndicator = _ref5.necessityIndicator, successText = _ref5.successText, errorText = _ref5.errorText, helpText = _ref5.helpText, format = _ref5.format, placeholder = _ref5.placeholder, setControlledValue = _ref5.setControlledValue, leadingDropdown = _ref5.leadingDropdown, selectedPreset = _ref5.selectedPreset, excludeDate = _ref5.excludeDate, minDate = _ref5.minDate, maxDate = _ref5.maxDate, effectiveSelectionType = _ref5.effectiveSelectionType, showClearButton = _ref5.showClearButton, onClearButtonClick = _ref5.onClearButtonClick, selectedPresetLabel = _ref5.selectedPresetLabel, props = _objectWithoutProperties(_ref5, _excluded3); var _useDatesContext = useDatesContext(), locale = _useDatesContext.locale; if (selectionType == 'single') { var dateValue = getFormattedDate({ date: date, format: format, labelSeparator: '-', locale: locale, type: 'default' }); return /*#__PURE__*/jsxs(BaseBox, { width: "100%", children: [/*#__PURE__*/jsx(HiddenInput, { value: dateValue, name: name, isRequired: props.isRequired, isDisabled: props.isDisabled }), /*#__PURE__*/jsx(DateInput, _objectSpread(_objectSpread({ ref: ref, id: "start-date", labelPosition: labelPosition, label: label, placeholder: placeholder || format, popupId: referenceProps['aria-controls'], isPopupExpanded: referenceProps['aria-expanded'], hasPopup: referenceProps['aria-haspopup'], size: size, autoFocus: autoFocus, value: [dateValue], componentName: "DatePickerInput", necessityIndicator: necessityIndicator, successText: successText, errorText: errorText, helpText: helpText, labelSuffix: labelSuffix, labelTrailing: labelTrailing, leadingDropdown: leadingDropdown, date: date, setControlledValue: setControlledValue, format: format, selectionType: selectionType, excludeDate: excludeDate, minDate: minDate, maxDate: maxDate, effectiveSelectionType: effectiveSelectionType, showClearButton: showClearButton, onClearButtonClick: onClearButtonClick, selectedPresetLabel: selectedPresetLabel }, props), referenceProps))] }); } if (selectionType == 'range') { var startValue = getFormattedDate({ type: 'default', date: date[0], format: format, labelSeparator: '-', locale: locale }); var endValue = getFormattedDate({ type: 'default', date: date[1], format: format, labelSeparator: '-', locale: locale }); return /*#__PURE__*/jsxs(BaseBox, { width: "100%", children: [/*#__PURE__*/jsx(HiddenInput, { value: "".concat(startValue), name: name === null || name === void 0 ? void 0 : name.start, isRequired: props.isRequired, isDisabled: props.isDisabled }), /*#__PURE__*/jsx(HiddenInput, _objectSpread({ value: endValue, name: name === null || name === void 0 ? void 0 : name.end, isRequired: props.isRequired, isDisabled: props.isDisabled }, makeAnalyticsAttribute(props))), /*#__PURE__*/jsx(DateInput, _objectSpread(_objectSpread({ ref: ref, id: "range-date", labelPosition: labelPosition, label: _typeof(label) === 'object' ? label === null || label === void 0 ? void 0 : label.start : label, placeholder: rangeInputPlaceHolder(placeholder, format), popupId: referenceProps['aria-controls'], isPopupExpanded: referenceProps['aria-expanded'], hasPopup: referenceProps['aria-haspopup'], size: size, autoFocus: autoFocus, value: [startValue, endValue], componentName: "DatePickerInputRange", necessityIndicator: necessityIndicator, successText: _typeof(successText) === 'object' ? successText === null || successText === void 0 ? void 0 : successText.start : successText, errorText: _typeof(errorText) === 'object' ? errorText === null || errorText === void 0 ? void 0 : errorText.start : errorText, helpText: _typeof(helpText) === 'object' ? helpText === null || helpText === void 0 ? void 0 : helpText.start : helpText, labelSuffix: labelSuffix, labelTrailing: labelTrailing, format: format, leadingDropdown: leadingDropdown, date: date, setControlledValue: setControlledValue, selectionType: selectionType, excludeDate: excludeDate, minDate: minDate, maxDate: maxDate, effectiveSelectionType: effectiveSelectionType, showClearButton: showClearButton, onClearButtonClick: onClearButtonClick, selectedPresetLabel: selectedPresetLabel }, props), referenceProps))] }); } return /*#__PURE__*/jsx(Fragment, {}); }; var DatePickerInput = /*#__PURE__*/React__default.forwardRef(_DatePickerInput); export { DatePickerInput }; //# sourceMappingURL=DateInput.web.js.map