rsuite
Version:
A suite of react components
250 lines (244 loc) • 8.34 kB
JavaScript
'use client';
import _extends from "@babel/runtime/helpers/esm/extends";
import React, { useRef, useMemo } from 'react';
import Input from "../Input/index.js";
import useDateInputState from "./hooks/useDateInputState.js";
import useKeyboardInputEvent from "./hooks/useKeyboardInputEvent.js";
import useIsFocused from "./hooks/useIsFocused.js";
import useFieldCursor from "./hooks/useFieldCursor.js";
import useSelectedState from "./hooks/useSelectedState.js";
import { useControlled, useEventCallback, useCustom } from "../internals/hooks/index.js";
import { forwardRef, mergeRefs } from "../internals/utils/index.js";
import { isValid } from "../internals/utils/date/index.js";
import { getInputSelectedState, validateDateTime, useInputSelection } from "./utils.js";
/**
* The DateInput component lets users select a date with the keyboard.
* @version 5.58.0
* @see https://rsuitejs.com/components/date-input/
*/
const DateInput = forwardRef((props, ref) => {
const {
propsWithDefaults,
parseDate,
getLocale
} = useCustom('DateInput', props);
const {
dateLocale,
shortDateFormat
} = getLocale('DateTimeFormats');
const {
format: formatStr = shortDateFormat,
value: valueProp,
defaultValue,
placeholder,
onChange,
onKeyDown,
onBlur,
onFocus,
onPaste,
...rest
} = propsWithDefaults;
const inputRef = useRef(null);
const {
selectedState,
setSelectedState
} = useSelectedState();
const [value, setValue, isControlled] = useControlled(valueProp, defaultValue);
const {
dateField,
setDateOffset,
setDateField,
setNewDate,
getDateField,
toDateString,
isEmptyValue
} = useDateInputState({
formatStr,
locale: dateLocale,
date: value,
isControlledDate: isControlled
});
const {
isMoveCursor,
isResetValue,
increment,
reset
} = useFieldCursor(formatStr, valueProp);
const dateString = toDateString();
const keyPressOptions = useMemo(() => ({
formatStr,
localize: dateLocale.localize,
selectedMonth: dateField.month,
dateString
}), [dateField, dateString, formatStr, dateLocale]);
const setSelectionRange = useInputSelection(inputRef);
const handleChange = useEventCallback((value, event) => {
onChange === null || onChange === void 0 || onChange(value, event);
setValue(value);
});
const handleClear = useEventCallback(event => {
handleChange(null, event);
setNewDate(null);
setSelectionRange(0, 0);
reset();
});
const onSegmentChange = useEventCallback((event, nextDirection) => {
const input = event.target;
const key = event.key;
const direction = nextDirection || (key === 'ArrowRight' ? 'right' : 'left');
const state = getInputSelectedState({
...keyPressOptions,
input,
direction
});
setSelectedState(state);
setSelectionRange(state.selectionStart, state.selectionEnd);
// If the selected field changes, reset the input state
if (selectedState.selectedPattern !== state.selectedPattern) {
reset();
}
});
const onSegmentValueChange = useEventCallback(event => {
const input = event.target;
const key = event.key;
const offset = key === 'ArrowUp' ? 1 : -1;
const state = getInputSelectedState({
...keyPressOptions,
input,
valueOffset: offset
});
setSelectedState(state);
setDateOffset(state.selectedPattern, offset, date => handleChange(date, event));
setSelectionRange(state.selectionStart, state.selectionEnd);
});
const onSegmentValueChangeWithNumericKeys = useEventCallback(event => {
const input = event.target;
const key = event.key;
const isFunctionKey = key.startsWith('F') && !isNaN(Number(key.slice(1)));
if (isFunctionKey) {
return;
}
const pattern = selectedState.selectedPattern;
if (!pattern) {
return;
}
const field = getDateField(pattern);
const value = parseInt(key, 10);
const padValue = parseInt(`${field.value || ''}${key}`, 10);
let newValue = value;
if (validateDateTime(field.name, padValue) && !isResetValue()) {
// Check if the value entered by the user is a valid date
newValue = padValue;
}
setDateField(pattern, newValue, date => handleChange(date, event));
// The currently selected month will be retained as a parameter of getInputSelectedState,
// but if the user enters a month, the month value will be replaced with the value entered by the user.
const selectedMonth = pattern === 'M' ? newValue : dateField.month;
const nextState = getInputSelectedState({
...keyPressOptions,
input,
selectedMonth
});
setSelectedState(nextState);
setSelectionRange(nextState.selectionStart, nextState.selectionEnd);
increment();
// If the field is full value, move the cursor to the next field
if (isMoveCursor(newValue, pattern) && input.selectionEnd !== input.value.length) {
onSegmentChange(event, 'right');
}
});
const onSegmentValueRemove = useEventCallback(event => {
const input = event.target;
const value = input.value;
// If the text is all selected, clear the value
if (input.selectionStart === 0 && value && input.selectionEnd === value.length) {
handleClear(event);
} else if (selectedState.selectedPattern) {
const nextState = getInputSelectedState({
...keyPressOptions,
input,
valueOffset: null
});
setSelectedState(nextState);
setSelectionRange(nextState.selectionStart, nextState.selectionEnd);
setDateField(selectedState.selectedPattern, null, date => handleChange(date, event));
reset();
}
});
const onAmPmToggle = useEventCallback(event => {
const input = event.target;
const key = event.key.toLowerCase();
// Only handle 'a' or 'p' keys when the selected pattern is 'a' (AM/PM)
if (selectedState.selectedPattern === 'a' && (key === 'a' || key === 'p')) {
const currentHour = dateField.hour || 0;
const isAM = currentHour < 12;
const isPM = currentHour >= 12;
// Toggle AM/PM based on the key pressed
// 'a' key -> set to AM, 'p' key -> set to PM
if (key === 'a' && isPM || key === 'p' && isAM) {
const state = getInputSelectedState({
...keyPressOptions,
input
});
setSelectedState(state);
setDateOffset('a', 1, date => handleChange(date, event));
setSelectionRange(state.selectionStart, state.selectionEnd);
}
}
});
const handleClick = useEventCallback(event => {
const input = event.target;
const state = getInputSelectedState({
...keyPressOptions,
input
});
setSelectedState(state);
setSelectionRange(state.selectionStart, state.selectionEnd);
if (selectedState.selectedPattern !== state.selectedPattern) {
reset();
}
});
const handlePaste = useEventCallback(event => {
var _event$clipboardData;
event.preventDefault();
const pasteText = (_event$clipboardData = event.clipboardData) === null || _event$clipboardData === void 0 ? void 0 : _event$clipboardData.getData('text');
const nextDate = parseDate(pasteText, formatStr);
if (isValid(nextDate)) {
handleChange(nextDate, event);
setNewDate(nextDate);
}
onPaste === null || onPaste === void 0 || onPaste(event);
});
const onKeyboardInput = useKeyboardInputEvent({
onSegmentChange,
onSegmentValueChange,
onSegmentValueChangeWithNumericKeys,
onSegmentValueRemove,
onAmPmToggle,
onKeyDown
});
const [focused, focusEventProps] = useIsFocused({
onBlur,
onFocus
});
const renderedValue = useMemo(() => {
if (!isEmptyValue()) {
return dateString;
}
return !focused ? '' : dateString;
}, [dateString, focused, isEmptyValue]);
return /*#__PURE__*/React.createElement(Input, _extends({
inputMode: focused ? 'numeric' : 'text',
autoComplete: "off",
autoCorrect: "off",
spellCheck: false,
ref: mergeRefs(inputRef, ref),
onKeyDown: onKeyboardInput,
onClick: handleClick,
onPaste: handlePaste,
value: renderedValue,
placeholder: placeholder || formatStr
}, focusEventProps, rest));
});
DateInput.displayName = 'DateInput';
export default DateInput;