@fluentui/react-northstar
Version:
A themable React component library.
421 lines (418 loc) • 18.4 kB
JavaScript
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