UNPKG

@yamada-ui/react

Version:

React UI components of the Yamada, by the Yamada, for the Yamada built with React and Emotion

571 lines (567 loc) • 18.7 kB
"use client"; import { isComposing, runKeyAction, visuallyHiddenAttributes } from "../../utils/dom.js"; import { useUpdateEffect } from "../../utils/effect.js"; import { mergeRefs } from "../../utils/ref.js"; import { utils_exports } from "../../utils/index.js"; import { useControllableState } from "../../hooks/use-controllable-state/index.js"; import { useI18n } from "../../providers/i18n-provider/i18n-provider.js"; import { useFieldProps } from "../field/use-field-props.js"; import { useCombobox } from "../../hooks/use-combobox/index.js"; import { useDateTimeFormat } from "../format/use-format-date-time.js"; import { DEFAULT_MAX_DATE, DEFAULT_MIN_DATE, getAdjustedMonth, isAfterDate, isBeforeDate, updateMaybeDateValue, useCalendarProps } from "../calendar/use-calendar.js"; import { cloneElement, isValidElement, useCallback, useEffect, useMemo, useRef, useState } from "react"; import { jsxs } from "react/jsx-runtime"; import dayjs from "dayjs"; //#region src/components/date-picker/use-date-picker.tsx const defaultRender = ({ count, focused, index, max, separator, value }) => { const last = count - 1 === index; return /* @__PURE__ */ jsxs("span", { style: { marginInlineEnd: "var(--gap)" }, children: [value, (!(0, utils_exports.isNumber)(max) || count < max) && focused || !last ? separator : null] }); }; const useDatePicker = ({ maxDate = DEFAULT_MAX_DATE, minDate = DEFAULT_MIN_DATE,...props } = {}) => { if (dayjs(minDate).isAfter(dayjs(maxDate))) maxDate = minDate; const { locale: defaultLocale, t } = useI18n("datePicker"); const { props: { id, ref, name, allowInput = true, allowInputBeyond = false, closeOnChange = false, multiple = false, closeOnSelect = !multiple, defaultInputValue, defaultMonth = /* @__PURE__ */ new Date(), range = false, defaultValue = range ? { end: void 0, start: void 0 } : multiple ? [] : void 0, disabled, focusOnClear = true, format, inputValue: inputValueProp, locale = defaultLocale, max, month: monthProp, openOnChange = true, openOnClick = true, openOnFocus = true, parseDate, pattern, placeholder: placeholderProp, readOnly, render = defaultRender, required, separator = range ? "-" : ",", value: valueProp, onChange: onChangeProp, onChangeMonth: onChangeMonthProp, onInputChange: onInputChangeProp,...computedProps }, ariaProps, dataProps, eventProps } = useFieldProps(props); const dateTimeFormat = useDateTimeFormat({ locale }); const { calendarFormat, inputFormat } = useMemo(() => { const { input: inputFormat$1,...calendarFormat$1 } = format ?? {}; return { calendarFormat: calendarFormat$1, inputFormat: inputFormat$1 !== null ? inputFormat$1 ?? { day: "numeric", month: "long", year: "numeric" } : void 0 }; }, [format]); const [calendarProps, rest] = useCalendarProps(computedProps); const { excludeDate } = calendarProps; const fieldRef = useRef(null); const contentRef = useRef(null); const startInputRef = useRef(null); const endInputRef = useRef(null); const focusByClickRef = useRef(false); const [focused, setFocused] = useState(false); const [value, setValue] = useControllableState({ defaultValue, value: valueProp, onChange: onChangeProp }); const [month, setMonth] = useControllableState({ defaultValue: () => { let value$1 = defaultMonth; if (dayjs(minDate).isAfter(dayjs(defaultMonth))) value$1 = dayjs(minDate).set("date", 1).toDate(); if (valueProp) value$1 = getAdjustedMonth(valueProp, value$1); else if (defaultValue) value$1 = getAdjustedMonth(defaultValue, value$1); return value$1; }, value: monthProp, onChange: onChangeMonthProp }); const stringToDate = useCallback((value$1, start = minDate, end = maxDate) => { let date = parseDate?.(value$1); if (!date && dayjs(value$1).isValid()) date = dayjs(value$1, locale).toDate(); if (date == null) return void 0; if (excludeDate?.(date)) return void 0; if (!allowInputBeyond) { if (isBeforeDate(date, start)) date = start; if (isAfterDate(date, end)) date = end; } return date; }, [ allowInputBeyond, excludeDate, locale, maxDate, minDate, parseDate ]); const dateToString = useCallback((date) => { if (date == null) return ""; return dateTimeFormat(date, inputFormat); }, [dateTimeFormat, inputFormat]); const [inputValue, setInputValue] = useControllableState({ defaultValue: () => { if (defaultInputValue) if ((0, utils_exports.isObject)(defaultInputValue)) { if (dayjs(defaultInputValue.start).isValid()) defaultInputValue.start = dateTimeFormat(dayjs(defaultInputValue.start).toDate(), inputFormat); if (dayjs(defaultInputValue.end).isValid()) defaultInputValue.end = dateTimeFormat(dayjs(defaultInputValue.end).toDate(), inputFormat); return defaultInputValue; } else if (dayjs(defaultInputValue).isValid()) return dateTimeFormat(dayjs(defaultInputValue).toDate(), inputFormat); else return defaultInputValue; else if ((0, utils_exports.isDate)(value)) return dateToString(value); else if ((0, utils_exports.isObject)(value) && !(0, utils_exports.isArray)(value)) return { end: dateToString(value.end), start: dateToString(value.start) }; else return ""; }, value: inputValueProp, onChange: onInputChangeProp }); const { interactive, open, getContentProps: getComboboxContentProps, getTriggerProps, popoverProps, onClose, onOpen } = useCombobox({ disabled, matchWidth: false, openOnClick: false, openOnEnter: !allowInput, openOnSpace: !allowInput, placement: "end-start", readOnly, transferFocus: false, ...ariaProps, ...dataProps, ...eventProps, ...rest }); const children = useMemo(() => { if (!(0, utils_exports.isArray)(value)) return null; const count = value.length; return value.map((date, index) => { const value$1 = dateToString(date); const onClear$1 = (ev) => { ev?.preventDefault(); ev?.stopPropagation(); setValue((prev) => updateMaybeDateValue(date, max)(prev)); }; const component = render({ count, date, focused, index, max, separator, value: value$1, onClear: onClear$1 }); if (isValidElement(component)) return cloneElement(component, { ...component.props, key: index }); else return component; }); }, [ dateToString, focused, max, render, separator, setValue, value ]); const { endPlaceholder, startPlaceholder } = useMemo(() => { if (range) { const startPlaceholder$1 = placeholderProp ?? dateTimeFormat(/* @__PURE__ */ new Date(), inputFormat); return { endPlaceholder: placeholderProp ?? dateTimeFormat(dayjs().add(1, "day").toDate(), inputFormat), startPlaceholder: startPlaceholder$1 }; } else { const placeholder = placeholderProp ?? dateTimeFormat(/* @__PURE__ */ new Date(), inputFormat); return { endPlaceholder: placeholder, startPlaceholder: placeholder }; } }, [ dateTimeFormat, inputFormat, placeholderProp, range ]); const onChange = useCallback((value$1) => { setValue(value$1); if ((0, utils_exports.isArray)(value$1)) setInputValue(""); else if ((0, utils_exports.isObject)(value$1) && !(0, utils_exports.isDate)(value$1)) setInputValue({ end: dateToString(value$1.end), start: dateToString(value$1.start) }); else setInputValue(dateToString(value$1)); if (!closeOnSelect) return; if ((0, utils_exports.isObject)(value$1) && !(0, utils_exports.isArray)(value$1) && !(0, utils_exports.isDate)(value$1)) { if (value$1.end && value$1.start) onClose(); } else onClose(); }, [ setValue, closeOnSelect, setInputValue, dateToString, onClose ]); const onInputChange = useCallback((ev) => { if (!allowInput) return; if ((0, utils_exports.isArray)(value) && value.length === max) return; if ((0, utils_exports.runIfFn)(closeOnChange, ev)) onClose(); else if ((0, utils_exports.runIfFn)(openOnChange, ev)) onOpen(); let inputValue$1 = ev.target.value; if (pattern) inputValue$1 = inputValue$1.replace(pattern, ""); if ((0, utils_exports.isArray)(value)) { const date = stringToDate(inputValue$1); setInputValue(inputValue$1); if (date) setMonth(dayjs(date).set("date", 1).toDate()); } else if ((0, utils_exports.isObject)(value) && !(0, utils_exports.isDate)(value)) { const align = (0, utils_exports.contains)(endInputRef.current, ev.target) ? "end" : "start"; const minDate$1 = value.start && align === "end" ? dayjs(value.start).add(1, "day").toDate() : void 0; const maxDate$1 = value.end && align === "start" ? dayjs(value.end).subtract(1, "day").toDate() : void 0; const date = stringToDate(inputValue$1, minDate$1, maxDate$1); setInputValue((prev) => (0, utils_exports.isObject)(prev) ? { ...prev, [align]: inputValue$1 } : prev); setValue((prev) => (0, utils_exports.isObject)(prev) && !(0, utils_exports.isDate)(prev) ? { ...prev, [align]: date } : prev); } else { const date = stringToDate(inputValue$1); setInputValue(inputValue$1); setValue(date); } }, [ allowInput, closeOnChange, max, onClose, onOpen, openOnChange, pattern, setInputValue, setMonth, setValue, stringToDate, value ]); const onKeyDown = useCallback((ev) => { if (disabled || isComposing(ev)) return; const inputValue$1 = (0, utils_exports.cast)(ev.target).value; runKeyAction(ev, { Backspace: (ev$1) => { if (!value || (0, utils_exports.isDate)(value)) return; if (!!inputValue$1.length) return; if ((0, utils_exports.isArray)(value)) { ev$1.preventDefault(); setValue((prev) => (0, utils_exports.isArray)(prev) ? prev.slice(0, -1) : prev); } else if ((0, utils_exports.contains)(endInputRef.current, ev$1.target)) { ev$1.preventDefault(); setInputValue({ end: "", start: "" }); setValue({ end: void 0, start: void 0 }); startInputRef.current?.focus(); } }, Enter: (ev$1) => { if (!open || !inputValue$1.length) return; if ((0, utils_exports.isDate)(value) || (0, utils_exports.isArray)(value)) { const date = stringToDate(inputValue$1); if (!date) return; ev$1.preventDefault(); if ((0, utils_exports.isArray)(value)) setInputValue(""); else setInputValue(dateToString(date)); setValue((prev) => updateMaybeDateValue(date, max)(prev)); } else if ((0, utils_exports.isObject)(value)) { const align = (0, utils_exports.contains)(endInputRef.current, ev$1.target) ? "end" : "start"; const date = stringToDate(inputValue$1, value.start && align === "end" ? dayjs(value.start).add(1, "day").toDate() : void 0, value.end && align === "start" ? dayjs(value.end).subtract(1, "day").toDate() : void 0); if (!date) return; ev$1.preventDefault(); setInputValue((prev) => (0, utils_exports.isObject)(prev) ? { ...prev, [align]: dateToString(date) } : prev); setValue((prev) => (0, utils_exports.isObject)(prev) && !(0, utils_exports.isDate)(prev) ? { ...prev, [align]: date } : prev); if (align === "start") endInputRef.current?.focus(); } } }, { preventDefault: false }); }, [ dateToString, disabled, max, open, setInputValue, setValue, stringToDate, value ]); const onClick = useCallback((ev) => { if (!interactive) return; focusByClickRef.current = true; if (allowInput) if ((0, utils_exports.isObject)(value) && !(0, utils_exports.isArray)(value) && !(0, utils_exports.isDate)(value)) { if ((0, utils_exports.contains)(startInputRef.current, ev.target)) return; if ((0, utils_exports.contains)(endInputRef.current, ev.target)) return; const { end, start } = value; if (!start && !end || !!end) startInputRef.current?.focus(); else endInputRef.current?.focus(); } else startInputRef.current?.focus(); if (openOnClick) onOpen(); }, [ allowInput, interactive, onOpen, openOnClick, value ]); const onMouseDown = useCallback((ev) => { if (!openOnFocus) return; ev.preventDefault(); ev.stopPropagation(); }, [openOnFocus]); const onFieldFocus = useCallback(() => { if (allowInput) return; if (openOnFocus) onOpen(); focusByClickRef.current = false; }, [ allowInput, onOpen, openOnFocus ]); const onInputFocus = useCallback((ev) => { ev.preventDefault(); ev.stopPropagation(); setFocused(true); if (openOnFocus && !focusByClickRef.current) onOpen(); focusByClickRef.current = false; }, [onOpen, openOnFocus]); const onBlur = useCallback((ev) => { setFocused(false); if ((0, utils_exports.contains)(fieldRef.current, ev.relatedTarget) || (0, utils_exports.contains)(contentRef.current, ev.relatedTarget)) ev.preventDefault(); else if ((0, utils_exports.isArray)(value)) setInputValue(""); else if ((0, utils_exports.isObject)(value) && !(0, utils_exports.isDate)(value)) setInputValue((prev) => (0, utils_exports.isObject)(prev) ? { end: dateToString(value.end), start: dateToString(value.start) } : prev); else setInputValue(dateToString(value)); }, [ dateToString, setInputValue, value ]); const onClear = useCallback(() => { if (!interactive) return; setValue((prev) => { if ((0, utils_exports.isDate)(prev)) return; else if ((0, utils_exports.isArray)(prev)) return []; else if ((0, utils_exports.isObject)(prev)) return { end: void 0, start: void 0 }; else return prev; }); setInputValue(range ? { end: "", start: "" } : ""); if (focusOnClear) if (allowInput) startInputRef.current?.focus(); else fieldRef.current?.focus(); }, [ allowInput, focusOnClear, interactive, range, setInputValue, setValue ]); useEffect(() => { if (!open) return; return (0, utils_exports.focusTransfer)(contentRef.current, allowInput ? startInputRef.current : fieldRef.current); }, [allowInput, open]); useUpdateEffect(() => { setMonth((prev) => getAdjustedMonth(value, prev)); }, [value, range]); useUpdateEffect(() => { if ((0, utils_exports.isArray)(valueProp)) return; if ((0, utils_exports.isObject)(valueProp) && !(0, utils_exports.isDate)(valueProp)) setInputValue({ end: dateToString(valueProp.end), start: dateToString(valueProp.start) }); else setInputValue(dateToString(valueProp)); }, [valueProp]); const getRootProps = useCallback((props$1) => ({ "data-range": (0, utils_exports.dataAttr)(range), ...dataProps, ...props$1 }), [dataProps, range]); const getFieldProps = useCallback(({ ref: ref$1,...props$1 } = {}) => getTriggerProps({ ref: mergeRefs(ref$1, fieldRef), "aria-haspopup": "dialog", tabIndex: !allowInput ? 0 : -1, ...props$1, onClick: (0, utils_exports.handlerAll)(props$1.onClick, onClick), onFocus: (0, utils_exports.handlerAll)(props$1.onFocus, onFieldFocus), onMouseDown: (0, utils_exports.handlerAll)(props$1.onMouseDown, onMouseDown) }), [ allowInput, getTriggerProps, onClick, onFieldFocus, onMouseDown ]); const getInputProps = useCallback(({ align = "start",...props$1 } = {}) => { const inputProps = { style: { ...!allowInput ? { pointerEvents: "none" } : {}, ...props$1.style }, autoComplete: "off", disabled, readOnly, required, tabIndex: allowInput ? 0 : -1, ...dataProps, ...props$1, onBlur: (0, utils_exports.handlerAll)(props$1.onBlur, onBlur), onChange: (0, utils_exports.handlerAll)(props$1.onChange, onInputChange), onFocus: (0, utils_exports.handlerAll)(props$1.onFocus, onInputFocus), onKeyDown: (0, utils_exports.handlerAll)(props$1.onKeyDown, onKeyDown), onMouseDown: (0, utils_exports.handlerAll)(props$1.onMouseDown, onMouseDown) }; if ((0, utils_exports.isObject)(inputValue)) { if (align === "start") { inputProps.ref = mergeRefs(props$1.ref, ref, startInputRef); inputProps.value = inputValue.start; inputProps.placeholder = startPlaceholder; } else { inputProps.ref = mergeRefs(props$1.ref, ref, endInputRef); inputProps.value = inputValue.end; inputProps.placeholder = endPlaceholder; } if (!inputValue.start && align === "start") { inputProps.id = id; inputProps.name = name; } if (!!inputValue.start && align === "end") { inputProps.id = id; inputProps.name = name; } } else { inputProps.ref = mergeRefs(props$1.ref, ref, startInputRef); inputProps.value = inputValue; inputProps.id = id; inputProps.name = name; if ((0, utils_exports.isArray)(value)) { inputProps.style = { ...!focused && !!value.length ? visuallyHiddenAttributes.style : {}, ...inputProps.style }; inputProps.placeholder = !value.length ? startPlaceholder : void 0; inputProps["data-max"] = (0, utils_exports.dataAttr)((0, utils_exports.isNumber)(max) && value.length >= max); } else inputProps.placeholder = startPlaceholder; } return inputProps; }, [ allowInput, dataProps, disabled, endPlaceholder, focused, id, inputValue, max, name, onBlur, onInputFocus, onInputChange, onKeyDown, onMouseDown, readOnly, ref, required, startPlaceholder, value ]); const getContentProps = useCallback(({ ref: ref$1,...props$1 } = {}) => getComboboxContentProps({ ref: mergeRefs(ref$1, contentRef), role: "dialog", ...props$1 }), [getComboboxContentProps]); const getCalendarProps = useCallback((props$1) => ({ disabled: !interactive, format: calendarFormat, locale, max, maxDate, minDate, month, multiple, range, value, onChange, onChangeMonth: setMonth, ...calendarProps, ...props$1 }), [ interactive, calendarFormat, locale, max, maxDate, minDate, month, multiple, range, value, onChange, setMonth, calendarProps ]); const getIconProps = useCallback((props$1) => ({ ...dataProps, ...props$1 }), [dataProps]); return { children, dateToString, inputValue, interactive, month, open, range, separator, setInputValue, setMonth, setValue, stringToDate, value, getCalendarProps, getClearIconProps: useCallback((props$1 = {}) => getIconProps({ "aria-disabled": (0, utils_exports.ariaAttr)(!interactive), "aria-label": t("Clear value"), role: "button", tabIndex: interactive ? 0 : -1, ...props$1, onClick: (0, utils_exports.handlerAll)(props$1.onClick, onClear), onKeyDown: (0, utils_exports.handlerAll)(props$1.onKeyDown, (ev) => runKeyAction(ev, { Enter: onClear, Space: onClear })) }), [ getIconProps, interactive, onClear, t ]), getContentProps, getFieldProps, getIconProps, getInputProps, getRootProps, popoverProps, onChange, onClose, onInputChange, onOpen }; }; //#endregion export { useDatePicker }; //# sourceMappingURL=use-date-picker.js.map