UNPKG

@fluentui/react-northstar

Version:
421 lines (418 loc) 18.4 kB
import _invoke from "lodash/invoke"; import { datepickerBehavior } from '@fluentui/accessibility'; import { DateRangeType, DayOfWeek, FirstWeekOfYear, DEFAULT_CALENDAR_STRINGS } from '../../utils/date-time-utilities'; import { getElementType, useAccessibility, useFluentContext, useStyles, useTelemetry, useUnhandledProps, useAutoControlled } from '@fluentui/react-bindings'; import { CalendarIcon } from '@fluentui/react-icons-northstar'; import * as customPropTypes from '@fluentui/react-proptypes'; import { handleRef } from '@fluentui/react-component-ref'; import * as PropTypes from 'prop-types'; import * as React from 'react'; import { commonPropTypes, createShorthand, createShorthandFactory, format } from '../../utils'; import { Button } from '../Button/Button'; import { Input } from '../Input/Input'; import { Popup } from '../Popup/Popup'; import { DatepickerCalendar } from './DatepickerCalendar'; import { DatepickerCalendarCell } from './DatepickerCalendarCell'; import { DatepickerCalendarCellButton } from './DatepickerCalendarCellButton'; import { DatepickerCalendarHeader } from './DatepickerCalendarHeader'; import { DatepickerCalendarHeaderAction } from './DatepickerCalendarHeaderAction'; import { DatepickerCalendarHeaderCell } from './DatepickerCalendarHeaderCell'; import { DatepickerCalendarGrid } from './DatepickerCalendarGrid'; import { DatepickerCalendarGridRow } from './DatepickerCalendarGridRow'; import { validateDate } from './validateDate'; export var datepickerClassName = 'ui-datepicker'; var formatRestrictedInput = function formatRestrictedInput(restrictedOptions, localizationStrings) { var formattedString = ''; if (!!restrictedOptions.minDate && !!restrictedOptions.maxDate) { formattedString = format(localizationStrings.inputBoundedFormatString, localizationStrings.formatMonthDayYear(restrictedOptions.minDate, localizationStrings), localizationStrings.formatMonthDayYear(restrictedOptions.maxDate, localizationStrings)); } else if (!!restrictedOptions.minDate) { formattedString = format(localizationStrings.inputMinBoundedFormatString, localizationStrings.formatMonthDayYear(restrictedOptions.minDate, localizationStrings)); } else if (!!restrictedOptions.maxDate) { formattedString = format(localizationStrings.inputMaxBoundedFormatString, localizationStrings.formatMonthDayYear(restrictedOptions.maxDate, localizationStrings)); } else { formattedString = localizationStrings.inputAriaLabel; } return formattedString; }; /** * A Datepicker is a control which is used to display dates grid and allow user to select them. * * @accessibilityIssues * [NVDA - Aria-selected is not narrated for the gridcell](https://github.com/nvaccess/nvda/issues/11986) */ export var Datepicker = /*#__PURE__*/function () { var Datepicker = /*#__PURE__*/React.forwardRef(function (props, ref) { var _props$today; var context = useFluentContext(); var _useTelemetry = useTelemetry(Datepicker.displayName, context.telemetry), setStart = _useTelemetry.setStart, setEnd = _useTelemetry.setEnd; setStart(); var _inputRef = React.useRef(); // FIXME: This object is created every render, causing a cascade of useCallback/useEffect re-runs. // Needs to be reworked by someone who understands the intent for when various updates ought to happen. // eslint-disable-next-line react-hooks/exhaustive-deps var dateFormatting = { formatDay: props.formatDay, formatYear: props.formatYear, formatMonthDayYear: props.formatMonthDayYear, formatMonthYear: props.formatMonthYear, parseDate: props.parseDate, months: props.months, shortMonths: props.shortMonths, days: props.days, shortDays: props.shortDays, isRequiredErrorMessage: props.isRequiredErrorMessage, invalidInputErrorMessage: props.invalidInputErrorMessage, isOutOfBoundsErrorMessage: props.isOutOfBoundsErrorMessage, openCalendarTitle: props.openCalendarTitle, inputPlaceholder: props.inputPlaceholder, prevMonthAriaLabel: props.prevMonthAriaLabel, nextMonthAriaLabel: props.nextMonthAriaLabel, prevYearAriaLabel: props.prevYearAriaLabel, nextYearAriaLabel: props.nextYearAriaLabel, prevYearRangeAriaLabel: props.prevYearRangeAriaLabel, nextYearRangeAriaLabel: props.nextYearRangeAriaLabel, monthPickerHeaderAriaLabel: props.monthPickerHeaderAriaLabel, yearPickerHeaderAriaLabel: props.yearPickerHeaderAriaLabel, closeButtonAriaLabel: props.closeButtonAriaLabel, weekNumberFormatString: props.weekNumberFormatString, selectedDateFormatString: props.selectedDateFormatString, todayDateFormatString: props.todayDateFormatString, inputAriaLabel: props.inputAriaLabel, inputBoundedFormatString: props.inputBoundedFormatString, inputMinBoundedFormatString: props.inputMinBoundedFormatString, inputMaxBoundedFormatString: props.inputMaxBoundedFormatString }; var calendar = props.calendar, popup = props.popup, input = props.input, className = props.className, design = props.design, styles = props.styles, variables = props.variables, formatMonthDayYear = props.formatMonthDayYear, allowManualInput = props.allowManualInput, ariaLabelledby = props['aria-labelledby'], ariaInvalid = props['aria-invalid']; var valueFormatter = React.useCallback(function (date) { return date ? formatMonthDayYear(date, { months: dateFormatting.months, shortMonths: dateFormatting.shortMonths, days: dateFormatting.days, shortDays: dateFormatting.shortDays }) : ''; }, [dateFormatting.days, dateFormatting.months, dateFormatting.shortDays, dateFormatting.shortMonths, formatMonthDayYear]); var _useAutoControlled = useAutoControlled({ defaultValue: props.defaultCalendarOpenState, value: props.calendarOpenState, initialValue: false }), openState = _useAutoControlled[0], setOpenState = _useAutoControlled[1]; var _useAutoControlled2 = useAutoControlled({ defaultValue: props.defaultSelectedDate, value: props.selectedDate, initialValue: undefined }), selectedDate = _useAutoControlled2[0], setSelectedDate = _useAutoControlled2[1]; var _React$useState = React.useState(valueFormatter(selectedDate)), formattedDate = _React$useState[0], setFormattedDate = _React$useState[1]; React.useEffect(function () { setFormattedDate(valueFormatter(selectedDate)); }, [selectedDate, valueFormatter]); var restrictedDatesOptions = { minDate: props.minDate, maxDate: props.maxDate, restrictedDates: props.restrictedDates }; var _React$useState2 = React.useState(function () { return !!props.selectedDate || !!props.defaultSelectedDate ? validateDate(selectedDate, formattedDate, restrictedDatesOptions, dateFormatting, props.required) : ''; }), error = _React$useState2[0], setError = _React$useState2[1]; var trySetOpenState = function trySetOpenState(newValue, event) { setOpenState(newValue); _invoke(props, 'onCalendarOpenStateChange', event, Object.assign({}, props, { calendarOpenState: newValue })); }; var calendarOptions = Object.assign({ selectedDate: selectedDate, navigatedDate: !!selectedDate && !error ? selectedDate : (_props$today = props.today) != null ? _props$today : new Date(), firstDayOfWeek: props.firstDayOfWeek, firstWeekOfYear: props.firstWeekOfYear, dateRangeType: props.dateRangeType, daysToSelectInDayView: props.daysToSelectInDayView, today: props.today, showWeekNumbers: props.showWeekNumbers, workWeekDays: props.workWeekDays }, restrictedDatesOptions); var ElementType = getElementType(props); var unhandledProps = useUnhandledProps(Datepicker.handledProps, props); var getA11yProps = useAccessibility(props.accessibility, { debugName: Datepicker.displayName, actionHandlers: { open: function open(e) { if (allowManualInput) { trySetOpenState(!openState, e); } else { // Keep popup open in case we can only enter the date through calendar. trySetOpenState(true, e); } e.preventDefault(); } }, mapPropsToBehavior: function mapPropsToBehavior() { return { 'aria-invalid': ariaInvalid, 'aria-labelledby': ariaLabelledby, allowManualInput: allowManualInput }; }, rtl: context.rtl }); var _useStyles = useStyles(Datepicker.displayName, { className: datepickerClassName, mapPropsToStyles: function mapPropsToStyles() { return { allowManualInput: allowManualInput }; }, mapPropsToInlineStyles: function mapPropsToInlineStyles() { return { className: className, design: design, styles: styles, variables: variables }; }, rtl: context.rtl }), classes = _useStyles.classes; var overrideDatepickerCalendarProps = function overrideDatepickerCalendarProps(predefinedProps) { return { onDateChange: function onDateChange(e, itemProps) { var targetDay = itemProps.value; setSelectedDate(targetDay.originalDate); trySetOpenState(false, e); setError(''); setFormattedDate(valueFormatter(targetDay.originalDate)); _invoke(props, 'onDateChange', e, { itemProps: itemProps, value: targetDay.originalDate }); } }; }; var calendarElement = createShorthand(DatepickerCalendar, calendar, { defaultProps: function defaultProps() { return getA11yProps('calendar', Object.assign({}, calendarOptions, dateFormatting)); }, overrideProps: overrideDatepickerCalendarProps }); var overrideInputProps = function overrideInputProps(predefinedProps) { return { onClick: function onClick(e) { if (allowManualInput) { trySetOpenState(!openState, e); } else { // Keep popup open in case we can only enter the date through calendar. trySetOpenState(true, e); } _invoke(predefinedProps, 'onClick', e, predefinedProps); }, onChange: function onChange(e, target) { var parsedDate = props.parseDate(target.value); var validationError = validateDate(parsedDate, target.value, calendarOptions, dateFormatting, props.required); setError(validationError); setFormattedDate(target.value); if (!!validationError) { _invoke(props, 'onDateChangeError', e, Object.assign({}, props, { error: validationError })); } else { setSelectedDate(parsedDate); _invoke(props, 'onDateChange', e, Object.assign({}, props, { value: parsedDate })); } _invoke(predefinedProps, 'onChange', e, predefinedProps); }, onBlur: function onBlur(e) { if (props.fallbackToLastCorrectDateOnBlur && !!error) { var futureFormattedDate = valueFormatter(selectedDate); var validationError = validateDate(selectedDate, futureFormattedDate, calendarOptions, dateFormatting, props.required); setError(validationError); setFormattedDate(futureFormattedDate); if (!!validationError) { _invoke(props, 'onDateChangeError', e, Object.assign({}, props, { error: validationError })); } } _invoke(predefinedProps, 'onBlur', e, predefinedProps); }, inputRef: function inputRef(node) { handleRef(predefinedProps.inputRef, node); _inputRef.current = node; } }; }; var triggerButtonElement = props.inputOnly ? null : /*#__PURE__*/React.createElement(Button, { icon: /*#__PURE__*/React.createElement(CalendarIcon, null), title: props.openCalendarTitle, iconOnly: true, disabled: props.disabled, type: "button" }); var element = getA11yProps.unstable_wrapWithFocusZone( /*#__PURE__*/React.createElement(ElementType, getA11yProps('root', Object.assign({ className: classes.root, ref: ref }, unhandledProps)), !props.buttonOnly && createShorthand(Input, input, { defaultProps: function defaultProps() { return getA11yProps('input', { placeholder: props.inputPlaceholder, disabled: props.disabled, error: !!error, value: formattedDate, readOnly: !allowManualInput, required: props.required, 'aria-label': formatRestrictedInput(restrictedDatesOptions, dateFormatting) }); }, overrideProps: overrideInputProps }), createShorthand(Popup, popup, { defaultProps: function defaultProps() { return { open: openState && !props.disabled, trapFocus: { disableFirstFocus: true }, position: 'below', align: 'start' }; }, overrideProps: function overrideProps(predefinedProps) { var _predefinedProps$trig; return { trigger: (_predefinedProps$trig = predefinedProps.trigger) != null ? _predefinedProps$trig : triggerButtonElement, target: props.buttonOnly ? null : _inputRef.current, content: calendarElement, onOpenChange: function onOpenChange(e, _ref) { var open = _ref.open; // In case the event is a click on input, we ignore such events as it should be directly handled by input. if (!(e.type === 'click' && e.target === (_inputRef == null ? void 0 : _inputRef.current))) { trySetOpenState(open, e); _invoke(predefinedProps, 'onOpenChange', e, { open: open }); } } }; } }))); setEnd(); return element; }); Datepicker.displayName = 'Datepicker'; Datepicker.propTypes = Object.assign({}, commonPropTypes.createCommon(), { calendar: customPropTypes.itemShorthand, popup: customPropTypes.itemShorthand, input: customPropTypes.itemShorthand, disabled: PropTypes.bool, required: PropTypes.bool, onDateChange: PropTypes.func, onDateChangeError: PropTypes.func, allowManualInput: PropTypes.bool, fallbackToLastCorrectDateOnBlur: PropTypes.bool, defaultCalendarOpenState: PropTypes.bool, calendarOpenState: PropTypes.bool, onCalendarOpenStateChange: PropTypes.func, selectedDate: PropTypes.instanceOf(Date), defaultSelectedDate: PropTypes.instanceOf(Date), inputOnly: PropTypes.bool, buttonOnly: PropTypes.bool, minDate: PropTypes.instanceOf(Date), maxDate: PropTypes.instanceOf(Date), restrictedDates: PropTypes.arrayOf(PropTypes.instanceOf(Date)), firstDayOfWeek: PropTypes.oneOf(Object.keys(DayOfWeek).map(function (name) { return DayOfWeek[name]; })), firstWeekOfYear: PropTypes.oneOf(Object.keys(FirstWeekOfYear).map(function (name) { return FirstWeekOfYear[name]; })), dateRangeType: PropTypes.oneOf(Object.keys(DateRangeType).map(function (name) { return DateRangeType[name]; })), daysToSelectInDayView: PropTypes.number, today: PropTypes.instanceOf(Date), showWeekNumbers: PropTypes.bool, workWeekDays: PropTypes.arrayOf(PropTypes.oneOf(Object.keys(DayOfWeek).map(function (name) { return DayOfWeek[name]; }))), formatDay: PropTypes.func, formatYear: PropTypes.func, formatMonthDayYear: PropTypes.func, formatMonthYear: PropTypes.func, parseDate: PropTypes.func, months: PropTypes.arrayOf(PropTypes.string), shortMonths: PropTypes.arrayOf(PropTypes.string), days: PropTypes.arrayOf(PropTypes.string), shortDays: PropTypes.arrayOf(PropTypes.string), isRequiredErrorMessage: PropTypes.string, invalidInputErrorMessage: PropTypes.string, isOutOfBoundsErrorMessage: PropTypes.string, openCalendarTitle: PropTypes.string, inputPlaceholder: PropTypes.string, prevMonthAriaLabel: PropTypes.string, nextMonthAriaLabel: PropTypes.string, prevYearAriaLabel: PropTypes.string, nextYearAriaLabel: PropTypes.string, prevYearRangeAriaLabel: PropTypes.string, nextYearRangeAriaLabel: PropTypes.string, monthPickerHeaderAriaLabel: PropTypes.string, yearPickerHeaderAriaLabel: PropTypes.string, closeButtonAriaLabel: PropTypes.string, weekNumberFormatString: PropTypes.string, selectedDateFormatString: PropTypes.string, todayDateFormatString: PropTypes.string, inputAriaLabel: PropTypes.string, inputBoundedFormatString: PropTypes.string, inputMinBoundedFormatString: PropTypes.string, inputMaxBoundedFormatString: PropTypes.string, 'aria-labelledby': PropTypes.string, 'aria-invalid': PropTypes.bool }); Datepicker.defaultProps = Object.assign({ accessibility: datepickerBehavior, inputOnly: false, buttonOnly: false, calendar: {}, popup: {}, input: {}, firstDayOfWeek: DayOfWeek.Monday, firstWeekOfYear: FirstWeekOfYear.FirstDay, dateRangeType: DateRangeType.Day, fallbackToLastCorrectDateOnBlur: true, allowManualInput: true, required: false }, DEFAULT_CALENDAR_STRINGS); Datepicker.handledProps = Object.keys(Datepicker.propTypes); Datepicker.create = createShorthandFactory({ Component: Datepicker }); Datepicker.Calendar = DatepickerCalendar; Datepicker.CalendarHeader = DatepickerCalendarHeader; Datepicker.CalendarHeaderAction = DatepickerCalendarHeaderAction; Datepicker.CalendarHeaderCell = DatepickerCalendarHeaderCell; Datepicker.CalendarCell = DatepickerCalendarCell; Datepicker.CalendarCellButton = DatepickerCalendarCellButton; Datepicker.CalendarGrid = DatepickerCalendarGrid; Datepicker.CalendarGridRow = DatepickerCalendarGridRow; Datepicker.Input = Input; return Datepicker; }(); //# sourceMappingURL=Datepicker.js.map