UNPKG

14 kBJavaScriptView Raw
1const _excluded = ["id", "value", "onChange", "onSelect", "onToggle", "onKeyDown", "onKeyPress", "onCurrentDateChange", "inputProps", "calendarProps", "timeInputProps", "autoFocus", "tabIndex", "disabled", "readOnly", "className", "valueFormat", "valueDisplayFormat", "valueEditFormat", "containerClassName", "name", "selectIcon", "placeholder", "includeTime", "min", "max", "open", "dropUp", "parse", "messages", "formats", "currentDate", "popupTransition", "popupComponent", "timePrecision", "aria-labelledby", "aria-describedby"];
2
3function _extends() { _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; return _extends.apply(this, arguments); }
4
5function _objectWithoutPropertiesLoose(source, excluded) { if (source == null) return {}; var target = {}; var sourceKeys = Object.keys(source); var key, i; for (i = 0; i < sourceKeys.length; i++) { key = sourceKeys[i]; if (excluded.indexOf(key) >= 0) continue; target[key] = source[key]; } return target; }
6
7import cn from 'classnames';
8import PropTypes from 'prop-types';
9import React, { useImperativeHandle, useRef, useCallback } from 'react';
10import { useUncontrolled } from 'uncontrollable';
11import Calendar from './Calendar';
12import DatePickerInput from './DatePickerInput';
13import { calendar } from './Icon';
14import { useLocalizer } from './Localization';
15import BasePopup from './Popup';
16import TimeInput from './TimeInput';
17import Widget from './Widget';
18import WidgetPicker from './WidgetPicker';
19import dates from './dates';
20import useDropdownToggle from './useDropdownToggle';
21import useTabTrap from './useTabTrap';
22import useFocusManager from './useFocusManager';
23import { notify, useFirstFocusedRender, useInstanceId } from './WidgetHelpers';
24import useEventCallback from '@restart/hooks/useEventCallback';
25import InputAddon from './InputAddon';
26let propTypes = {
27 /**
28 * @example ['valuePicker', [ ['new Date()', null] ]]
29 */
30 value: PropTypes.instanceOf(Date),
31
32 /**
33 * @example ['onChangePicker', [ ['new Date()', null] ]]
34 */
35 onChange: PropTypes.func,
36
37 /**
38 * @example ['openDate']
39 */
40 open: PropTypes.bool,
41 onToggle: PropTypes.func,
42
43 /**
44 * Default current date at which the calendar opens. If none is provided, opens at today's date or the `value` date (if any).
45 */
46 currentDate: PropTypes.instanceOf(Date),
47
48 /**
49 * Change event Handler that is called when the currentDate is changed. The handler is called with the currentDate object.
50 */
51 onCurrentDateChange: PropTypes.func,
52 onSelect: PropTypes.func,
53
54 /**
55 * The minimum Date that can be selected. Min only limits selection, it doesn't constrain the date values that
56 * can be typed or pasted into the widget. If you need this behavior you can constrain values via
57 * the `onChange` handler.
58 *
59 * @example ['prop', ['min', 'new Date()']]
60 */
61 min: PropTypes.instanceOf(Date),
62
63 /**
64 * The maximum Date that can be selected. Max only limits selection, it doesn't constrain the date values that
65 * can be typed or pasted into the widget. If you need this behavior you can constrain values via
66 * the `onChange` handler.
67 *
68 * @example ['prop', ['max', 'new Date()']]
69 */
70 max: PropTypes.instanceOf(Date),
71
72 /**
73 * A formatting options used to display the date value. This is a shorthand for
74 * setting both `valueDisplayFormat` and `valueEditFormat`.
75 */
76 valueFormat: PropTypes.any,
77
78 /**
79 * A formatting options used to display the date value. For more information about formats
80 * visit the [Localization page](./localization)
81 *
82 * ```tsx live
83 * import { DatePicker } from 'react-widgets';
84 *
85 * <DatePicker
86 * defaultValue={new Date()}
87 * valueDisplayFormat={{ dateStyle: "medium" }}
88 * />
89 * ```
90 */
91 valueDisplayFormat: PropTypes.any,
92
93 /**
94 * A formatting options used while the date input has focus. Useful for showing a simpler format for inputing.
95 * For more information about formats visit the [Localization page](./localization)
96 *
97 * ```tsx live
98 * import { DatePicker } from 'react-widgets';
99 *
100 * <DatePicker
101 * defaultValue={new Date()}
102 * valueEditFormat={{ dateStyle: "short" }}
103 * valueDisplayFormat={{ dateStyle: "medium" }}
104 * />
105 * ```
106 */
107 valueEditFormat: PropTypes.any,
108
109 /**
110 * Enable the time list component of the picker.
111 */
112 includeTime: PropTypes.bool,
113 timePrecision: PropTypes.oneOf(['minutes', 'seconds', 'milliseconds']),
114 timeInputProps: PropTypes.object,
115
116 /** Specify the element used to render the calendar dropdown icon. */
117 selectIcon: PropTypes.node,
118 dropUp: PropTypes.bool,
119 popupTransition: PropTypes.elementType,
120 placeholder: PropTypes.string,
121 name: PropTypes.string,
122 autoFocus: PropTypes.bool,
123
124 /**
125 * @example ['disabled', ['new Date()']]
126 */
127 disabled: PropTypes.bool,
128
129 /**
130 * @example ['readOnly', ['new Date()']]
131 */
132 readOnly: PropTypes.bool,
133
134 /**
135 * Determines how the widget parses the typed date string into a Date object. You can provide an array of formats to try,
136 * or provide a function that returns a date to handle parsing yourself. When `parse` is unspecified and
137 * the `format` prop is a `string` parse will automatically use that format as its default.
138 */
139 parse: PropTypes.oneOfType([PropTypes.any, PropTypes.func]),
140
141 /** @ignore */
142 tabIndex: PropTypes.any,
143
144 /** @ignore */
145 'aria-labelledby': PropTypes.string,
146
147 /** @ignore */
148 'aria-describedby': PropTypes.string,
149
150 /** @ignore */
151 localizer: PropTypes.any,
152 onKeyDown: PropTypes.func,
153 onKeyPress: PropTypes.func,
154 onBlur: PropTypes.func,
155 onFocus: PropTypes.func,
156
157 /** Adds a css class to the input container element. */
158 containerClassName: PropTypes.string,
159 calendarProps: PropTypes.object,
160 inputProps: PropTypes.object,
161 messages: PropTypes.shape({
162 dateButton: PropTypes.string
163 })
164};
165const defaultProps = Object.assign({}, Calendar.defaultProps, {
166 min: new Date(1900, 0, 1),
167 max: new Date(2099, 11, 31),
168 selectIcon: calendar,
169 formats: {}
170});
171
172/**
173 * ---
174 * subtitle: DatePicker, TimePicker
175 * localized: true
176 * shortcuts:
177 * - { key: alt + down arrow, label: open calendar or time }
178 * - { key: alt + up arrow, label: close calendar or time }
179 * - { key: down arrow, label: move focus to next item }
180 * - { key: up arrow, label: move focus to previous item }
181 * - { key: home, label: move focus to first item }
182 * - { key: end, label: move focus to last item }
183 * - { key: enter, label: select focused item }
184 * - { key: any key, label: search list for item starting with key }
185 * ---
186 *
187 * @public
188 * @extends Calendar
189 */
190const DatePicker = /*#__PURE__*/React.forwardRef((uncontrolledProps, outerRef) => {
191 const _useUncontrolled = useUncontrolled(uncontrolledProps, {
192 open: 'onToggle',
193 value: 'onChange',
194 currentDate: 'onCurrentDateChange'
195 }),
196 {
197 id,
198 value,
199 onChange,
200 onSelect,
201 onToggle,
202 onKeyDown,
203 onKeyPress,
204 onCurrentDateChange,
205 inputProps,
206 calendarProps,
207 timeInputProps,
208 autoFocus,
209 tabIndex,
210 disabled,
211 readOnly,
212 className,
213 // @ts-ignore
214 valueFormat,
215 valueDisplayFormat = valueFormat,
216 valueEditFormat = valueFormat,
217 containerClassName,
218 name,
219 selectIcon,
220 placeholder,
221 includeTime = false,
222 min,
223 max,
224 open,
225 dropUp,
226 parse,
227 messages,
228 formats,
229 currentDate,
230 popupTransition,
231 popupComponent: Popup = BasePopup,
232 timePrecision,
233 'aria-labelledby': ariaLabelledby,
234 'aria-describedby': ariaDescribedby
235 } = _useUncontrolled,
236 elementProps = _objectWithoutPropertiesLoose(_useUncontrolled, _excluded);
237
238 const localizer = useLocalizer(messages, formats);
239 const ref = useRef(null);
240 const calRef = useRef(null);
241 const tabTrap = useTabTrap(calRef);
242 const inputId = useInstanceId(id, '_input');
243 const dateId = useInstanceId(id, '_date');
244 const currentFormat = includeTime ? 'datetime' : 'date';
245 const toggle = useDropdownToggle(open, onToggle);
246 const [focusEvents, focused] = useFocusManager(ref, uncontrolledProps, {
247 didHandle(focused) {
248 if (!focused) {
249 toggle.close();
250 tabTrap.stop();
251 } else if (open) {
252 tabTrap.focus();
253 }
254 }
255
256 });
257 const dateParser = useCallback(str => {
258 var _localizer$parseDate, _ref;
259
260 if (typeof parse == 'function') {
261 var _parse;
262
263 return (_parse = parse(str, localizer)) != null ? _parse : null;
264 }
265
266 return (_localizer$parseDate = localizer.parseDate(str, (_ref = parse != null ? parse : valueEditFormat) != null ? _ref : valueDisplayFormat)) != null ? _localizer$parseDate : null;
267 }, [localizer, parse, valueDisplayFormat, valueEditFormat]);
268 /**
269 * Handlers
270 */
271
272 const handleChange = useEventCallback((date, str, constrain) => {
273 if (readOnly || disabled) return;
274 if (constrain) date = inRangeValue(date);
275
276 if (onChange) {
277 if (date == null || value == null) {
278 if (date != value //eslint-disable-line eqeqeq
279 ) onChange(date, str);
280 } else if (!dates.eq(date, value)) {
281 onChange(date, str);
282 }
283 }
284 });
285 const handleKeyDown = useEventCallback(e => {
286 if (readOnly) return;
287 notify(onKeyDown, [e]);
288 if (e.defaultPrevented) return;
289
290 if (e.key === 'Escape' && open) {
291 toggle.close();
292 } else if (e.altKey) {
293 if (e.key === 'ArrowDown') {
294 e.preventDefault();
295 toggle.open();
296 } else if (e.key === 'ArrowUp') {
297 e.preventDefault();
298 toggle.close();
299 }
300 }
301 });
302 const handleKeyPress = useEventCallback(e => {
303 notify(onKeyPress, [e]);
304 if (e.defaultPrevented) return;
305 });
306 const handleDateSelect = useEventCallback(date => {
307 var _ref$current;
308
309 let dateTime = dates.merge(date, value, currentDate);
310 let dateStr = formatDate(date);
311 if (!includeTime) toggle.close();
312 notify(onSelect, [dateTime, dateStr]);
313 handleChange(dateTime, dateStr, true);
314 (_ref$current = ref.current) == null ? void 0 : _ref$current.focus();
315 });
316 const handleTimeChange = useEventCallback(date => {
317 handleChange(date, formatDate(date), true);
318 });
319 const handleCalendarClick = useEventCallback(e => {
320 if (readOnly || disabled) return; // prevents double clicks when in a <label>
321
322 e.preventDefault();
323 toggle();
324 });
325
326 const handleOpening = () => {
327 tabTrap.start();
328 requestAnimationFrame(() => {
329 tabTrap.focus();
330 });
331 };
332
333 const handleClosing = () => {
334 tabTrap.stop();
335 if (focused) focus();
336 };
337 /**
338 * Methods
339 */
340
341
342 function focus() {
343 var _calRef$current, _ref$current2;
344
345 if (open) (_calRef$current = calRef.current) == null ? void 0 : _calRef$current.focus();else (_ref$current2 = ref.current) == null ? void 0 : _ref$current2.focus();
346 }
347
348 function inRangeValue(value) {
349 if (value == null) return value;
350 return dates.max(dates.min(value, max), min);
351 }
352
353 function formatDate(date) {
354 return date instanceof Date && !isNaN(date.getTime()) ? localizer.formatDate(date, currentFormat) : '';
355 }
356
357 useImperativeHandle(outerRef, () => ({
358 focus
359 }));
360 let shouldRenderList = useFirstFocusedRender(focused, open);
361 const inputReadOnly = (inputProps == null ? void 0 : inputProps.readOnly) != null ? inputProps == null ? void 0 : inputProps.readOnly : readOnly;
362 return /*#__PURE__*/React.createElement(Widget, _extends({}, elementProps, {
363 defaultValue: undefined,
364 open: !!open,
365 dropUp: dropUp,
366 focused: focused,
367 disabled: disabled,
368 readOnly: readOnly,
369 onKeyDown: handleKeyDown,
370 onKeyPress: handleKeyPress
371 }, focusEvents, {
372 className: cn(className, 'rw-date-picker')
373 }), /*#__PURE__*/React.createElement(WidgetPicker, {
374 className: containerClassName
375 }, /*#__PURE__*/React.createElement(DatePickerInput, _extends({}, inputProps, {
376 id: inputId,
377 ref: ref,
378 role: "combobox",
379 name: name,
380 value: value,
381 tabIndex: tabIndex,
382 autoFocus: autoFocus,
383 placeholder: placeholder,
384 disabled: disabled,
385 readOnly: inputReadOnly,
386 formatter: currentFormat,
387 displayFormat: valueDisplayFormat,
388 editFormat: valueEditFormat,
389 editing: focused,
390 localizer: localizer,
391 parse: dateParser,
392 onChange: handleChange,
393 "aria-haspopup": true,
394 "aria-labelledby": ariaLabelledby,
395 "aria-describedby": ariaDescribedby,
396 "aria-expanded": !!open,
397 "aria-owns": dateId
398 })), /*#__PURE__*/React.createElement(InputAddon, {
399 icon: selectIcon,
400 label: localizer.messages.dateButton(),
401 disabled: disabled || readOnly,
402 onClick: handleCalendarClick
403 })), !!shouldRenderList && /*#__PURE__*/React.createElement(Popup, {
404 dropUp: dropUp,
405 open: open,
406 role: "dialog",
407 ref: calRef,
408 id: dateId,
409 className: "rw-calendar-popup",
410 transition: popupTransition,
411 onEntering: handleOpening,
412 onExited: handleClosing
413 }, /*#__PURE__*/React.createElement(Calendar, _extends({
414 min: min,
415 max: max,
416 bordered: false
417 }, calendarProps, {
418 messages: Object.assign({}, messages, calendarProps == null ? void 0 : calendarProps.messages),
419 tabIndex: -1,
420 value: value,
421 autoFocus: false,
422 onChange: handleDateSelect,
423 currentDate: currentDate,
424 onCurrentDateChange: onCurrentDateChange,
425 "aria-hidden": !open,
426 "aria-live": "polite",
427 "aria-labelledby": inputId
428 })), includeTime && /*#__PURE__*/React.createElement(TimeInput, _extends({}, timeInputProps, {
429 value: value,
430 precision: timePrecision,
431 onChange: handleTimeChange,
432 datePart: currentDate
433 }))));
434});
435DatePicker.displayName = 'DatePicker';
436DatePicker.propTypes = propTypes;
437DatePicker.defaultProps = defaultProps;
438export default DatePicker;
\No newline at end of file