1 | const _excluded = ["id", "autoFocus", "bordered", "views", "tabIndex", "disabled", "readOnly", "className", "value", "defaultValue", "onChange", "currentDate", "defaultCurrentDate", "onCurrentDateChange", "min", "max", "view", "defaultView", "onViewChange", "onKeyDown", "onNavigate", "renderDay", "messages", "formats"];
|
2 |
|
3 | function _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 |
|
5 | function _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 |
|
7 | import cn from 'classnames';
|
8 | import PropTypes from 'prop-types';
|
9 | import React, { useEffect, useRef } from 'react';
|
10 | import { useUncontrolledProp } from 'uncontrollable';
|
11 | import CalendarHeader from './CalendarHeader';
|
12 | import Century from './Century';
|
13 | import Decade from './Decade';
|
14 | import { useLocalizer } from './Localization';
|
15 | import Month from './Month';
|
16 | import SlideTransitionGroup from './SlideTransitionGroup';
|
17 | import Widget from './Widget';
|
18 | import Year from './Year';
|
19 | import dates from './dates';
|
20 | import useAutoFocus from './useAutoFocus';
|
21 | import useFocusManager from './useFocusManager';
|
22 | import { notify, useInstanceId } from './WidgetHelpers';
|
23 |
|
24 | let last = a => a[a.length - 1];
|
25 |
|
26 | const CELL_CLASSNAME = 'rw-cell';
|
27 | const FOCUSED_CELL_SELECTOR = `.${CELL_CLASSNAME}[tabindex]`;
|
28 | const MIN = new Date(1900, 0, 1);
|
29 | const MAX = new Date(2099, 11, 31);
|
30 | const VIEW_OPTIONS = ['month', 'year', 'decade', 'century'];
|
31 | const VIEW_UNIT = {
|
32 | month: 'day',
|
33 | year: 'month',
|
34 | decade: 'year',
|
35 | century: 'decade'
|
36 | };
|
37 | const VIEW = {
|
38 | month: Month,
|
39 | year: Year,
|
40 | decade: Decade,
|
41 | century: Century
|
42 | };
|
43 | const ARROWS_TO_DIRECTION = {
|
44 | ArrowDown: 'DOWN',
|
45 | ArrowUp: 'UP',
|
46 | ArrowRight: 'RIGHT',
|
47 | ArrowLeft: 'LEFT'
|
48 | };
|
49 | const OPPOSITE_DIRECTION = {
|
50 | LEFT: 'RIGHT',
|
51 | RIGHT: 'LEFT'
|
52 | };
|
53 | const MULTIPLIER = {
|
54 | year: 1,
|
55 | decade: 10,
|
56 | century: 100
|
57 | };
|
58 |
|
59 | function inRangeValue(_value, min, max) {
|
60 | let value = dateOrNull(_value);
|
61 | if (value === null) return value;
|
62 | return dates.max(dates.min(value, max), min);
|
63 | }
|
64 |
|
65 | const propTypes = {
|
66 | |
67 |
|
68 |
|
69 | disabled: PropTypes.bool,
|
70 |
|
71 | |
72 |
|
73 |
|
74 | readOnly: PropTypes.bool,
|
75 |
|
76 | |
77 |
|
78 |
|
79 | onChange: PropTypes.func,
|
80 |
|
81 | |
82 |
|
83 |
|
84 |
|
85 |
|
86 |
|
87 |
|
88 |
|
89 |
|
90 |
|
91 | value: PropTypes.instanceOf(Date),
|
92 |
|
93 | |
94 |
|
95 |
|
96 |
|
97 |
|
98 | min: PropTypes.instanceOf(Date),
|
99 |
|
100 | |
101 |
|
102 |
|
103 |
|
104 |
|
105 | max: PropTypes.instanceOf(Date),
|
106 |
|
107 | |
108 |
|
109 |
|
110 | currentDate: PropTypes.instanceOf(Date),
|
111 |
|
112 | |
113 |
|
114 |
|
115 | onCurrentDateChange: PropTypes.func,
|
116 |
|
117 |
|
118 | navigatePrevIcon: PropTypes.node,
|
119 |
|
120 |
|
121 | navigateNextIcon: PropTypes.node,
|
122 |
|
123 | |
124 |
|
125 |
|
126 |
|
127 |
|
128 |
|
129 | view(props, ...args) {
|
130 |
|
131 | return PropTypes.oneOf(props.views || VIEW_OPTIONS)(props, ...args);
|
132 | },
|
133 |
|
134 | |
135 |
|
136 |
|
137 |
|
138 |
|
139 |
|
140 | views: PropTypes.arrayOf(PropTypes.oneOf(VIEW_OPTIONS)),
|
141 |
|
142 | |
143 |
|
144 |
|
145 |
|
146 |
|
147 | onViewChange: PropTypes.func,
|
148 |
|
149 | |
150 |
|
151 |
|
152 |
|
153 |
|
154 | onNavigate: PropTypes.func,
|
155 | culture: PropTypes.string,
|
156 | autoFocus: PropTypes.bool,
|
157 |
|
158 | |
159 |
|
160 |
|
161 |
|
162 |
|
163 | footer: PropTypes.bool,
|
164 |
|
165 | |
166 |
|
167 |
|
168 |
|
169 |
|
170 |
|
171 | renderDay: PropTypes.func,
|
172 | formats: PropTypes.shape({
|
173 | |
174 |
|
175 |
|
176 |
|
177 |
|
178 | header: PropTypes.any,
|
179 |
|
180 | |
181 |
|
182 |
|
183 |
|
184 |
|
185 | footer: PropTypes.any,
|
186 |
|
187 | |
188 |
|
189 |
|
190 |
|
191 |
|
192 | day: PropTypes.any,
|
193 |
|
194 | |
195 |
|
196 |
|
197 |
|
198 |
|
199 | date: PropTypes.any,
|
200 |
|
201 | |
202 |
|
203 |
|
204 |
|
205 |
|
206 | month: PropTypes.any,
|
207 |
|
208 | |
209 |
|
210 |
|
211 |
|
212 |
|
213 | year: PropTypes.any,
|
214 |
|
215 | |
216 |
|
217 |
|
218 | decade: PropTypes.any,
|
219 |
|
220 | |
221 |
|
222 |
|
223 | century: PropTypes.any
|
224 | }),
|
225 | messages: PropTypes.shape({
|
226 | moveBack: PropTypes.string,
|
227 | moveForward: PropTypes.string
|
228 | }),
|
229 | onKeyDown: PropTypes.func,
|
230 |
|
231 |
|
232 | tabIndex: PropTypes.any
|
233 | };
|
234 |
|
235 | const useViewState = (views, view = views[0], currentDate) => {
|
236 | const lastView = useRef(view);
|
237 | const lastDate = useRef(currentDate);
|
238 | let slideDirection;
|
239 |
|
240 | if (view !== lastView.current) {
|
241 | slideDirection = views.indexOf(lastView.current) > views.indexOf(view) ? 'top' : 'bottom';
|
242 | } else if (lastDate.current !== currentDate) {
|
243 | slideDirection = dates.gt(currentDate, lastDate.current) ? 'left' : 'right';
|
244 | }
|
245 |
|
246 | useEffect(() => {
|
247 | lastDate.current = currentDate;
|
248 | lastView.current = view;
|
249 | });
|
250 | return slideDirection;
|
251 | };
|
252 |
|
253 |
|
254 |
|
255 |
|
256 | function Calendar(_ref) {
|
257 | let {
|
258 | id,
|
259 | autoFocus,
|
260 | bordered = true,
|
261 | views = VIEW_OPTIONS,
|
262 | tabIndex = 0,
|
263 | disabled,
|
264 | readOnly,
|
265 | className,
|
266 | value,
|
267 | defaultValue,
|
268 | onChange,
|
269 | currentDate: pCurrentDate,
|
270 | defaultCurrentDate,
|
271 | onCurrentDateChange,
|
272 | min = MIN,
|
273 | max = MAX,
|
274 | view,
|
275 | defaultView = views[0],
|
276 | onViewChange,
|
277 | onKeyDown,
|
278 | onNavigate,
|
279 | renderDay,
|
280 | messages,
|
281 | formats
|
282 | } = _ref,
|
283 | elementProps = _objectWithoutPropertiesLoose(_ref, _excluded);
|
284 |
|
285 | const [currentValue, handleChange] = useUncontrolledProp(value, defaultValue, onChange);
|
286 | const [currentDate, handleCurrentDateChange] = useUncontrolledProp(pCurrentDate, defaultCurrentDate || currentValue || new Date(), onCurrentDateChange);
|
287 | const [currentView, handleViewChange] = useUncontrolledProp(view, defaultView, onViewChange);
|
288 | const localizer = useLocalizer(messages, formats);
|
289 | const ref = useRef(null);
|
290 | const viewId = useInstanceId(id, '_calendar');
|
291 | const labelId = useInstanceId(id, '_calendar_label');
|
292 | useAutoFocus(!!autoFocus, ref);
|
293 | const slideDirection = useViewState(views, currentView, currentDate);
|
294 | const [, focused] = useFocusManager(ref, {
|
295 | disabled
|
296 | }, {
|
297 | willHandle() {
|
298 | if (tabIndex == -1) return false;
|
299 | }
|
300 |
|
301 | });
|
302 | const lastValue = useRef(currentValue);
|
303 | useEffect(() => {
|
304 | const inValue = inRangeValue(currentValue, min, max);
|
305 | const last = lastValue.current;
|
306 | lastValue.current = currentValue;
|
307 | if (!dates.eq(inValue, dateOrNull(last), VIEW_UNIT[currentView])) maybeSetCurrentDate(inValue);
|
308 | });
|
309 | const isDisabled = disabled || readOnly;
|
310 | |
311 |
|
312 |
|
313 |
|
314 | const handleViewChangeImpl = () => {
|
315 | navigate('UP');
|
316 | };
|
317 |
|
318 | const handleMoveBack = () => {
|
319 | navigate('LEFT');
|
320 | };
|
321 |
|
322 | const handleMoveForward = () => {
|
323 | navigate('RIGHT');
|
324 | };
|
325 |
|
326 | const handleDateChange = date => {
|
327 | if (views[0] === currentView) {
|
328 | maybeSetCurrentDate(date);
|
329 | notify(handleChange, [date]);
|
330 | focus();
|
331 | return;
|
332 | }
|
333 |
|
334 | navigate('DOWN', date);
|
335 | };
|
336 |
|
337 | const handleMoveToday = () => {
|
338 | let date = new Date();
|
339 | let firstView = views[0];
|
340 | notify(onChange, [date]);
|
341 |
|
342 | if (dates.inRange(date, min, max, firstView)) {
|
343 | focus();
|
344 | maybeSetCurrentDate(date);
|
345 | notify(handleViewChange, [firstView]);
|
346 | }
|
347 | };
|
348 |
|
349 | const handleKeyDown = e => {
|
350 | let ctrl = e.ctrlKey || e.metaKey;
|
351 | let key = e.key;
|
352 | let direction = ARROWS_TO_DIRECTION[key];
|
353 | let unit = VIEW_UNIT[currentView];
|
354 |
|
355 | if (key === 'Enter') {
|
356 | e.preventDefault();
|
357 | return handleDateChange(currentDate);
|
358 | }
|
359 |
|
360 | if (direction) {
|
361 | if (ctrl) {
|
362 | e.preventDefault();
|
363 | navigate(direction);
|
364 | } else {
|
365 | const isRTL = getComputedStyle(e.currentTarget).getPropertyValue('direction') === 'rtl';
|
366 | if (isRTL && direction in OPPOSITE_DIRECTION) direction = OPPOSITE_DIRECTION[direction];
|
367 | let nextDate = Calendar.move(currentDate, min, max, currentView, direction);
|
368 |
|
369 | if (!dates.eq(currentDate, nextDate, unit)) {
|
370 | e.preventDefault();
|
371 | if (dates.gt(nextDate, currentDate, currentView)) navigate('RIGHT', nextDate);else if (dates.lt(nextDate, currentDate, currentView)) navigate('LEFT', nextDate);else maybeSetCurrentDate(nextDate);
|
372 | }
|
373 | }
|
374 | }
|
375 |
|
376 | notify(onKeyDown, [e]);
|
377 | };
|
378 |
|
379 | function navigate(direction, date) {
|
380 | let nextView = currentView;
|
381 | let slideDir = direction === 'LEFT' || direction === 'UP' ? 'right' : 'left';
|
382 | if (direction === 'UP') nextView = views[views.indexOf(currentView) + 1] || nextView;
|
383 | if (direction === 'DOWN') nextView = views[views.indexOf(currentView) - 1] || nextView;
|
384 | if (!date) date = ['LEFT', 'RIGHT'].indexOf(direction) !== -1 ? nextDate(direction) : currentDate;
|
385 |
|
386 | if (dates.inRange(date, min, max, nextView)) {
|
387 | notify(onNavigate, [date, slideDir, nextView]);
|
388 |
|
389 | maybeSetCurrentDate(date);
|
390 | notify(handleViewChange, [nextView]);
|
391 | }
|
392 | }
|
393 |
|
394 | const focus = () => {
|
395 | var _ref$current;
|
396 |
|
397 | const node = (_ref$current = ref.current) == null ? void 0 : _ref$current.querySelector(FOCUSED_CELL_SELECTOR);
|
398 | node == null ? void 0 : node.focus();
|
399 | };
|
400 |
|
401 | const moveFocus = (node, hadFocus) => {
|
402 | let current = document.activeElement;
|
403 |
|
404 | if (hadFocus && (!current || !node.contains(current))) {
|
405 | node.focus();
|
406 | }
|
407 | };
|
408 |
|
409 | function maybeSetCurrentDate(date) {
|
410 | let inRangeDate = inRangeValue(date ? new Date(date) : currentDate, min, max);
|
411 | if (date === currentDate || dates.eq(inRangeDate, dateOrNull(currentDate), VIEW_UNIT[currentView])) return;
|
412 | notify(handleCurrentDateChange, [inRangeDate]);
|
413 | }
|
414 |
|
415 | function nextDate(direction) {
|
416 | let method = direction === 'LEFT' ? 'subtract' : 'add';
|
417 | let unit = currentView === 'month' ? currentView : 'year';
|
418 | let multi = MULTIPLIER[currentView] || 1;
|
419 | return dates[method](currentDate, 1 * multi, unit);
|
420 | }
|
421 |
|
422 | function getHeaderLabel() {
|
423 | switch (currentView) {
|
424 | case 'month':
|
425 | return localizer.formatDate(currentDate, 'header');
|
426 |
|
427 | case 'year':
|
428 | return localizer.formatDate(currentDate, 'year');
|
429 |
|
430 | case 'decade':
|
431 | return localizer.formatDate(dates.startOf(currentDate, 'decade'), 'decade');
|
432 |
|
433 | case 'century':
|
434 | return localizer.formatDate(dates.startOf(currentDate, 'century'), 'century');
|
435 | }
|
436 | }
|
437 |
|
438 | let View = VIEW[currentView];
|
439 | let todayNotInRange = !dates.inRange(new Date(), min, max, currentView);
|
440 | let key = currentView + '_' + dates[currentView](currentDate);
|
441 |
|
442 |
|
443 | const prevDisabled = isDisabled || !dates.inRange(nextDate('LEFT'), min, max, currentView);
|
444 | const nextDisabled = isDisabled || !dates.inRange(nextDate('RIGHT'), min, max, currentView);
|
445 | return React.createElement(Widget, _extends({}, elementProps, {
|
446 | role: "group",
|
447 | ref: ref,
|
448 | focused: focused,
|
449 | disabled: disabled,
|
450 | readOnly: readOnly,
|
451 | tabIndex: tabIndex,
|
452 | className: cn(className, 'rw-calendar', bordered && 'rw-calendar-contained')
|
453 | }), React.createElement(CalendarHeader, {
|
454 | label: getHeaderLabel(),
|
455 | labelId: labelId,
|
456 | localizer: localizer,
|
457 | upDisabled: isDisabled || currentView === last(views),
|
458 | prevDisabled: prevDisabled,
|
459 | todayDisabled: isDisabled || todayNotInRange,
|
460 | nextDisabled: nextDisabled,
|
461 | onViewChange: handleViewChangeImpl,
|
462 | onMoveLeft: handleMoveBack,
|
463 | onMoveRight: handleMoveForward,
|
464 | onMoveToday: handleMoveToday
|
465 | }), React.createElement(Calendar.Transition, {
|
466 | direction: slideDirection,
|
467 | onTransitionEnd: moveFocus
|
468 | }, React.createElement(View, {
|
469 | key: key,
|
470 | min: min,
|
471 | max: max,
|
472 | id: viewId,
|
473 | value: currentValue,
|
474 | localizer: localizer,
|
475 | disabled: isDisabled,
|
476 | focusedItem: currentDate,
|
477 | onChange: handleDateChange,
|
478 | onKeyDown: handleKeyDown,
|
479 | "aria-labelledby": labelId,
|
480 | renderDay: renderDay
|
481 | })));
|
482 | }
|
483 |
|
484 | function dateOrNull(dt) {
|
485 | if (dt && !isNaN(dt.getTime())) return dt;
|
486 | return null;
|
487 | }
|
488 |
|
489 | Calendar.displayName = 'Calendar';
|
490 | Calendar.propTypes = propTypes;
|
491 |
|
492 |
|
493 |
|
494 |
|
495 |
|
496 |
|
497 | Calendar.Transition = SlideTransitionGroup;
|
498 |
|
499 | Calendar.move = (date, min, max, view, direction) => {
|
500 | let isMonth = view === 'month';
|
501 | let isUpOrDown = direction === 'UP' || direction === 'DOWN';
|
502 | let rangeUnit = view && VIEW_UNIT[view];
|
503 | let addUnit = isMonth && isUpOrDown ? 'week' : VIEW_UNIT[view];
|
504 | let amount = isMonth || !isUpOrDown ? 1 : 4;
|
505 | let newDate;
|
506 | if (direction === 'UP' || direction === 'LEFT') amount *= -1;
|
507 | newDate = dates.add(date, amount, addUnit);
|
508 | return dates.inRange(newDate, min, max, rangeUnit) ? newDate : date;
|
509 | };
|
510 |
|
511 | export default Calendar; |
\ | No newline at end of file |