UNPKG

@dnb/eufemia

Version:

DNB Eufemia Design System UI Library

404 lines (403 loc) 14.1 kB
"use client"; import _extends from "@babel/runtime/helpers/esm/extends"; var _AlignmentHelper; import React, { useCallback, useContext, useEffect, useMemo, useRef, useState } from 'react'; import classnames from 'classnames'; import { warn, extendPropsWithContext, getStatusState, combineDescribedBy, validateDOMAttributes } from "../../shared/component-helper.js"; import AlignmentHelper from "../../shared/AlignmentHelper.js"; import { createSpacingClasses } from "../space/SpacingHelper.js"; import { skeletonDOMAttributes } from "../skeleton/SkeletonHelper.js"; import Context from "../../shared/Context.js"; import Suffix from "../../shared/helpers/Suffix.js"; import FormLabel from "../form-label/FormLabel.js"; import FormStatus from "../form-status/FormStatus.js"; import DatePickerProvider from "./DatePickerProvider.js"; import DatePickerRange from "./DatePickerRange.js"; import DatePickerInput from "./DatePickerInput.js"; import DatePickerAddon from "./DatePickerAddon.js"; import DatePickerFooter from "./DatePickerFooter.js"; import { pickFormElementProps } from "../../shared/helpers/filterValidProps.js"; import { useTranslation } from "../../shared/index.js"; import { convertSnakeCaseProps } from "../../shared/helpers/withSnakeCaseProps.js"; import Popover from "../popover/Popover.js"; import { formatDate, formatDateRange } from "../date-format/DateFormatUtils.js"; import useId from "../../shared/helpers/useId.js"; const defaultProps = { hideNavigation: false, hideDays: false, onlyMonth: false, hideLastWeek: false, disableAutofocus: false, enableKeyboardNav: false, showInput: false, inline: false, resetDate: true, range: false, link: false, sync: true, statusState: 'error', opened: false, noAnimation: false, direction: 'auto', skipPortal: false, yearNavigation: false }; function DatePicker(externalProps) { const props = { ...defaultProps, ...externalProps }; const { preventClose, onHide, onShow, onSubmit, onCancel, onReset, noAnimation, showInput, inline, alignPicker, showSubmitButton, showCancelButton, range, hideDays, hideNavigation, correctInvalidDate, opened: openedProp, endDate: endDateProp } = convertSnakeCaseProps(props); const [opened, setOpened] = useState(inline ? true : openedProp); const [hidden, setHidden] = useState(inline ? false : !opened); const [dates, setDates] = useState({}); const context = useContext(Context); const blurDelay = 201; const id = useId(props.id); const innerRef = useRef(); const submitButtonRef = useRef(); const getReturnObject = useRef(); const hideTimeout = useRef(); const calendarContainerRef = useRef(null); const translation = useTranslation().DatePicker; const focusCalendarTable = useCallback(() => calendarContainerRef.current?.querySelector('table'), []); if (correctInvalidDate) { warn(`Use 'Field.Date' instead, for built in validation (https://eufemia.dnb.no/uilib/extensions/forms/feature-fields/Date/#date-limit-validation).`); } if (endDateProp && !range) { warn(`The DatePicker got a "endDate". You have to set range={true} as well!.`); } const hidePicker = useCallback(args => { if (preventClose) { return; } if (args && args.event && args.event.persist) { args.event.persist(); } setOpened(false); hideTimeout.current = setTimeout(() => { setHidden(true); onHide?.({ ...getReturnObject.current(args) }); if (args?.['focusOnHide']) { try { submitButtonRef.current.focus({ preventScroll: true }); } catch (e) { warn(e); } } }, noAnimation ? 1 : blurDelay); }, [noAnimation, preventClose, onHide]); const showPicker = useCallback(event => { if (hideTimeout.current) { clearTimeout(hideTimeout.current); } setOpened(true); setHidden(false); onShow?.({ ...getReturnObject.current(event) }); }, [onShow]); useEffect(() => { if (openedProp && !inline) { showPicker(); } }, [openedProp, showPicker, inline]); const onPickerChange = useCallback(({ hidePicker: shouldHidePicker = true, ...args }) => { if (shouldHidePicker && !showSubmitButton && !showCancelButton) { hidePicker({ focusOnHide: true }); } setDates({ startDate: args.startDate, endDate: args.endDate }); }, [hidePicker, showSubmitButton, showCancelButton]); const onSubmitHandler = useCallback(event => { if (opened) { hidePicker(event); onSubmit?.({ ...getReturnObject.current({ event }) }); } else { showPicker(event); } }, [opened, hidePicker, showPicker, onSubmit]); const onCancelHandler = useCallback(event => { hidePicker(); onCancel?.({ ...getReturnObject.current(event) }); }, [hidePicker, onCancel]); const onResetHandler = useCallback(event => { hidePicker(); onReset?.({ ...getReturnObject.current(event) }); }, [hidePicker, onReset]); const togglePicker = useCallback(args => { !opened ? showPicker(args) : hidePicker(args); }, [opened, showPicker, hidePicker]); const extendedProps = extendPropsWithContext(props, defaultProps, { skeleton: context?.skeleton }, convertSnakeCaseProps(context.getTranslation(props).DatePicker), pickFormElementProps(context?.FormRow), convertSnakeCaseProps(pickFormElementProps(context?.formElement)), context.DatePicker); const { label, title, labelDirection, labelSrOnly, onlyMonth, hideLastWeek, disableAutofocus, firstDay, resetDate, link, sync, inputElement, addonElement, shortcuts, disabled, stretch, skeleton, size, status, statusState, statusProps, statusNoAnimation, globalStatus, suffix, maskOrder, maskPlaceholder, submitButtonText, cancelButtonText, resetButtonText, showResetButton, className, tooltip, skipPortal, labelAlignment, ...restProps } = extendedProps; const attributes = useMemo(() => filterOutNonAttributes(restProps), [restProps]); const showStatus = getStatusState(status); const pickerParams = {}; if (showStatus || suffix) { pickerParams['aria-describedby'] = combineDescribedBy(pickerParams, showStatus ? id + '-status' : null, suffix ? id + '-suffix' : null); } const submitParams = { ['aria-expanded']: opened, ref: submitButtonRef, tabIndex: extendedProps.tabIndex, tooltip }; const selectedDateTitle = useMemo(() => { const { selectedDate, selectedDateRange } = translation; const { startDate, endDate } = dates; if (!startDate) { return ''; } const options = { locale: context.locale, options: { dateStyle: 'full' } }; return range && endDate ? selectedDateRange.replace(/%s/, formatDateRange({ startDate, endDate }, options)) : selectedDate.replace(/%s/, formatDate(startDate, options)); }, [range, translation, dates, context.locale]); const mainParams = { className: classnames("dnb-date-picker dnb-form-component", createSpacingClasses(props), className, status && `dnb-date-picker__status--${statusState}`, labelDirection && `dnb-date-picker--${labelDirection}`, opened && 'dnb-date-picker--opened', hidden && 'dnb-date-picker--hidden', showInput && 'dnb-date-picker--show-input', inline && 'dnb-date-picker--inline', label && labelAlignment === 'right' && 'dnb-date-picker__input--label-alignment-right', stretch && `dnb-date-picker--stretch`, size && `dnb-date-picker--${size}`), lang: context.locale }; const containerClassNames = classnames('dnb-date-picker__container', ((inline ? false : range) || showSubmitButton || showCancelButton || showResetButton) && 'dnb-date-picker__container--show-footer', opened ? 'dnb-date-picker__container--opened' : 'dnb-date-picker__container--closed', hidden && 'dnb-date-picker__container--hidden', showInput && 'dnb-date-picker__container--show-input', size && `dnb-date-picker--${size}`); const remainingDOMProps = validateDOMAttributes(props, attributes); const remainingSubmitProps = validateDOMAttributes(null, submitParams); const remainingPickerProps = validateDOMAttributes(null, skeletonDOMAttributes(pickerParams, skeleton, context)); return React.createElement(DatePickerProvider, _extends({}, props, { attributes: remainingDOMProps, setReturnObject: fn => getReturnObject.current = fn, hidePicker: hidePicker }), React.createElement("span", mainParams, label && React.createElement(FormLabel, { id: id + '-label', forId: id, text: label, labelDirection: labelDirection, srOnly: labelSrOnly, disabled: disabled, skeleton: skeleton }), React.createElement("span", _extends({ className: "dnb-date-picker__inner", ref: innerRef }, remainingPickerProps), _AlignmentHelper || (_AlignmentHelper = React.createElement(AlignmentHelper, null)), React.createElement(FormStatus, _extends({ show: showStatus, id: id + '-form-status', globalStatus: globalStatus, label: String(label), text_id: id + '-status', width_selector: id + '-shell', text: status, state: statusState, no_animation: statusNoAnimation, skeleton: skeleton }, statusProps)), React.createElement("span", { className: "dnb-date-picker__row" }, inline ? React.createElement("span", { className: containerClassNames, ref: calendarContainerRef }, React.createElement(DatePickerRange, { id: id, firstDayOfWeek: firstDay, resetDate: resetDate, isRange: range, isLink: link, isSync: sync, hideDays: hideDays, hideNavigation: hideNavigation, onlyMonth: onlyMonth, hideNextMonthWeek: hideLastWeek, onPickerChange: onPickerChange, locale: context.locale }), (addonElement || shortcuts) && React.createElement(DatePickerAddon, { renderElement: addonElement, shortcuts: shortcuts }), React.createElement(DatePickerFooter, { isRange: inline ? false : range, onSubmit: onSubmitHandler, onCancel: onCancelHandler, onReset: onResetHandler, submitButtonText: submitButtonText, cancelButtonText: cancelButtonText, resetButtonText: resetButtonText })) : React.createElement("span", { className: "dnb-date-picker__shell", id: `${id}-shell` }, React.createElement(DatePickerInput, _extends({ id: id, title: title, disabled: disabled, stretch: stretch, skeleton: skeleton, maskOrder: maskOrder, maskPlaceholder: maskPlaceholder, isRange: range, showInput: showInput, selectedDateTitle: selectedDateTitle, inputElement: inputElement, opened: opened, hidden: hidden, size: size, status: status ? 'error' : null, statusState: statusState, lang: context.locale }, attributes, { submitAttributes: remainingSubmitProps, onSubmit: togglePicker }, statusProps)), React.createElement(Popover, { open: opened, targetElement: { verticalRef: submitButtonRef, horizontalRef: innerRef }, noAnimation: noAnimation, skipPortal: skipPortal, keepInDOM: !hidden, focusOnOpen: !disableAutofocus, focusOnOpenElement: focusCalendarTable, alignOnTarget: alignPicker === 'right' || stretch ? 'right' : 'left', horizontalOffset: showInput ? 8 : -8, placement: props.direction === 'auto' ? 'bottom' : props.direction, onOpenChange: isOpen => !isOpen && hidePicker(), hideCloseButton: true, hideOutline: true, preventClose: preventClose, triggerOffset: 0, arrowEdgeOffset: 4, noInnerSpace: true, noMaxWidth: true, portalRootClass: "dnb-date-picker__portal", arrowPosition: alignPicker === 'right' ? 'right' : 'left', arrowPositionSelector: `#${id}` }, React.createElement("span", { className: containerClassNames, ref: calendarContainerRef }, React.createElement(DatePickerRange, { id: id, firstDayOfWeek: firstDay, resetDate: resetDate, isRange: range, isLink: link, isSync: sync, hideDays: hideDays, hideNavigation: hideNavigation, onlyMonth: onlyMonth, hideNextMonthWeek: hideLastWeek, onPickerChange: onPickerChange, locale: context.locale }), (addonElement || shortcuts) && React.createElement(DatePickerAddon, { renderElement: addonElement, shortcuts: shortcuts }), React.createElement(DatePickerFooter, { isRange: inline ? false : range, onSubmit: onSubmitHandler, onCancel: onCancelHandler, onReset: onResetHandler, submitButtonText: submitButtonText, cancelButtonText: cancelButtonText, resetButtonText: resetButtonText })))), suffix && React.createElement(Suffix, { className: "dnb-date-picker__suffix", id: id + '-suffix', context: props }, suffix))), React.createElement("p", { className: "dnb-sr-only", "aria-live": "assertive" }, selectedDateTitle))); } const NonAttributes = ['locale', 'id', 'month', 'date', 'startDate', 'endDate', 'minDate', 'maxDate', 'enableKeyboardNav', 'hideNavigation', 'returnFormat', 'dateFormat', 'hideDays', 'correctInvalidDate', 'opened', 'direction', 'range', 'showInput', 'inline', 'noAnimation', 'onDaysRender', 'onShow', 'onType', 'onHide', 'showSubmitButton', 'showCancelButton', 'selectedDate', 'selectedMonth', 'selectedYear', 'nextMonth', 'nextYear', 'openPickerText', 'placeholderCharacters', 'prevMonth', 'prevYear', 'endMonth', 'startMonth', 'alignPicker', 'preventClose', 'selectedDateRange', 'yearNavigation']; function filterOutNonAttributes(props) { return Object.keys(props).reduce((attributes, key) => { if (!NonAttributes.includes(key)) { attributes[key] = props[key]; } return attributes; }, {}); } export default DatePicker; DatePicker._supportsSpacingProps = true; //# sourceMappingURL=DatePicker.js.map