UNPKG

@dnb/eufemia

Version:

DNB Eufemia Design System UI Library

390 lines (389 loc) 16.1 kB
"use client"; import _extends from "@babel/runtime/helpers/esm/extends"; import _objectWithoutProperties from "@babel/runtime/helpers/esm/objectWithoutProperties"; import _defineProperty from "@babel/runtime/helpers/esm/defineProperty"; var _AlignmentHelper; const _excluded = ["hidePicker"], _excluded2 = ["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"]; import "core-js/modules/es.string.replace.js"; import "core-js/modules/web.dom-collections.iterator.js"; 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; } import React, { useCallback, useContext, useEffect, useLayoutEffect, useMemo, useRef, useState } from 'react'; import classnames from 'classnames'; import { warn, makeUniqueId, extendPropsWithContext, detectOutsideClick, getStatusState, combineDescribedBy, validateDOMAttributes } from '../../shared/component-helper'; import AlignmentHelper from '../../shared/AlignmentHelper'; import { createSpacingClasses } from '../space/SpacingHelper'; import { skeletonDOMAttributes } from '../skeleton/SkeletonHelper'; import Context from '../../shared/Context'; import Suffix from '../../shared/helpers/Suffix'; import FormLabel from '../form-label/FormLabel'; import FormStatus from '../form-status/FormStatus'; import DatePickerProvider from './DatePickerProvider'; import DatePickerRange from './DatePickerRange'; import DatePickerInput from './DatePickerInput'; import DatePickerAddon from './DatePickerAddon'; import DatePickerFooter from './DatePickerFooter'; import { pickFormElementProps } from '../../shared/helpers/filterValidProps'; import { useTranslation } from '../../shared'; import { convertSnakeCaseProps } from '../../shared/helpers/withSnakeCaseProps'; import DatePickerPortal from './DatePickerPortal'; import { formatDate, formatDateRange } from '../date-format/DateFormatUtils'; const defaultProps = { hideNavigation: false, hideDays: false, onlyMonth: false, hideLastWeek: false, disableAutofocus: false, enableKeyboardNav: false, showInput: 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 = _objectSpread(_objectSpread({}, defaultProps), externalProps); const { preventClose, onHide, onShow, onSubmit, onCancel, onReset, noAnimation, showInput, alignPicker, showSubmitButton, showCancelButton, range, hideDays, hideNavigation, correctInvalidDate, opened: openedProp, endDate: endDateProp } = convertSnakeCaseProps(props); const [opened, setOpened] = useState(openedProp); const [hidden, setHidden] = useState(!opened); const [dates, setDates] = useState({}); const context = useContext(Context); const blurDelay = 201; const id = props.id || makeUniqueId(); const innerRef = useRef(); const triangleRef = useRef(); const submitButtonRef = useRef(); const getReturnObject = useRef(); const hideTimeout = useRef(); const outsideClick = useRef(); const calendarContainerRef = useRef(); const translation = useTranslation().DatePicker; 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 removeOutsideClickHandler = useCallback(() => { if (outsideClick.current) { outsideClick.current.remove(); } }, []); 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 === null || onHide === void 0 ? void 0 : onHide(_objectSpread({}, getReturnObject.current(args))); if (args !== null && args !== void 0 && args['focusOnHide']) { try { submitButtonRef.current.focus({ preventScroll: true }); } catch (e) { warn(e); } } }, noAnimation ? 1 : blurDelay); removeOutsideClickHandler(); }, [noAnimation, preventClose, onHide, removeOutsideClickHandler]); const setOutsideClickHandler = useCallback(() => { outsideClick.current = detectOutsideClick([innerRef.current, calendarContainerRef], _ref => { let { event } = _ref; hidePicker(_objectSpread(_objectSpread({}, event), {}, { focusOnHide: event === null || event === void 0 ? void 0 : event['code'] })); }); }, [hidePicker]); const setTrianglePosition = useCallback(() => { const triangleWidth = 16; if (showInput && triangleRef.current && innerRef.current) { try { const shellWidth = innerRef.current.querySelector('.dnb-input__shell').getBoundingClientRect().width; const buttonWidth = innerRef.current.querySelector('.dnb-input__submit-button__button').getBoundingClientRect().width; if (alignPicker === 'right') { const distance = buttonWidth / 2 - triangleWidth / 2; triangleRef.current.style.marginRight = `${distance / 16}rem`; } else { const distance = shellWidth - buttonWidth / 2 - triangleWidth / 2; triangleRef.current.style.marginLeft = `${distance / 16}rem`; } } catch (e) { warn(e); } } }, [showInput, alignPicker]); const showPicker = useCallback(event => { if (hideTimeout.current) { clearTimeout(hideTimeout.current); } setOpened(true); setHidden(false); onShow === null || onShow === void 0 ? void 0 : onShow(_objectSpread({}, getReturnObject.current(event))); setOutsideClickHandler(); }, [setOutsideClickHandler, onShow]); useLayoutEffect(() => { if (!hidden) { setTrianglePosition(); } }, [hidden, setTrianglePosition]); useEffect(() => { if (openedProp) { showPicker(); } }, [openedProp, showPicker]); const onPickerChange = useCallback(_ref2 => { let { hidePicker: shouldHidePicker = true } = _ref2, args = _objectWithoutProperties(_ref2, _excluded); if (shouldHidePicker && !showSubmitButton && !showCancelButton) { hidePicker({ focusOnHide: true }); } setDates({ startDate: args.startDate, endDate: args.endDate }); }, [hidePicker, showSubmitButton, showCancelButton]); const onSubmitHandler = useCallback(event => { hidePicker(event); onSubmit === null || onSubmit === void 0 ? void 0 : onSubmit(_objectSpread({}, getReturnObject.current({ event }))); }, [hidePicker, onSubmit]); const onCancelHandler = useCallback(event => { hidePicker(); onCancel === null || onCancel === void 0 ? void 0 : onCancel(_objectSpread({}, getReturnObject.current(event))); }, [hidePicker, onCancel]); const onResetHandler = useCallback(event => { hidePicker(); onReset === null || onReset === void 0 ? void 0 : onReset(_objectSpread({}, getReturnObject.current(event))); }, [hidePicker, onReset]); const togglePicker = useCallback(args => { !opened ? showPicker(args) : hidePicker(args); }, [opened, showPicker, hidePicker]); const extendedProps = extendPropsWithContext(props, defaultProps, { skeleton: context === null || context === void 0 ? void 0 : context.skeleton }, convertSnakeCaseProps(context.getTranslation(props).DatePicker), pickFormElementProps(context === null || context === void 0 ? void 0 : context.FormRow), convertSnakeCaseProps(pickFormElementProps(context === null || context === void 0 ? void 0 : 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 } = extendedProps, restProps = _objectWithoutProperties(extendedProps, _excluded2); 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", (range || showSubmitButton || showCancelButton || showResetButton) && 'dnb-date-picker--show-footer', 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', label && labelAlignment === 'right' && 'dnb-date-picker__input--label-alignment-right', alignPicker && `dnb-date-picker--${alignPicker}`, stretch && `dnb-date-picker--stretch`, size && `dnb-date-picker--${size}`), lang: context.locale }; const containerClassNames = classnames('dnb-date-picker__container', opened ? 'dnb-date-picker__container--opened' : 'dnb-date-picker__container--closed', hidden && 'dnb-date-picker__container--hidden', showInput && 'dnb-date-picker__container--show-input', alignPicker && `dnb-date-picker__container--${alignPicker}`, 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" }, 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)), !hidden && React.createElement(DatePickerPortal, { alignment: alignPicker, skipPortal: skipPortal, targetElementRef: innerRef }, React.createElement("span", { className: containerClassNames, ref: calendarContainerRef }, React.createElement("span", { className: "dnb-date-picker__triangle", ref: triangleRef }), React.createElement(DatePickerRange, { id: id, firstDayOfWeek: firstDay, resetDate: resetDate, isRange: range, isLink: link, isSync: sync, hideDays: hideDays, hideNavigation: hideNavigation, onlyMonth: onlyMonth, hideNextMonthWeek: hideLastWeek, noAutoFocus: disableAutofocus, onPickerChange: onPickerChange, locale: context.locale }), (addonElement || shortcuts) && React.createElement(DatePickerAddon, { renderElement: addonElement, shortcuts: shortcuts }), React.createElement(DatePickerFooter, { isRange: 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', '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