wf-react-day-picker 0.0.1-security → 2.653.0

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of wf-react-day-picker might be problematic. Click here for more details.

@@ -0,0 +1,611 @@
1
+ import React from 'react';
2
+ import PropTypes from 'prop-types';
3
+
4
+ import DayPicker from './DayPicker';
5
+ import {isSameMonth, isDate} from './DateUtils';
6
+ import {getModifiersForDay} from './ModifiersUtils';
7
+ import {ESC, TAB} from './keys';
8
+
9
+ // When clicking on a day cell, overlay will be hidden after this timeout
10
+ export const HIDE_TIMEOUT = 100;
11
+
12
+ /**
13
+ * The default component used as Overlay.
14
+ *
15
+ * @param {Object} props
16
+ */
17
+ export function OverlayComponent({
18
+ input,
19
+ selectedDay,
20
+ month,
21
+ children,
22
+ classNames,
23
+ ...props
24
+ }) {
25
+ return (
26
+ <div className={classNames.overlayWrapper} {...props}>
27
+ <div className={classNames.overlay}>{children}</div>
28
+ </div>
29
+ );
30
+ }
31
+
32
+ OverlayComponent.propTypes = {
33
+ input: PropTypes.any,
34
+ selectedDay: PropTypes.any,
35
+ month: PropTypes.instanceOf(Date),
36
+ children: PropTypes.node,
37
+ classNames: PropTypes.object,
38
+ };
39
+
40
+ /**
41
+ * The default function used to format a Date to String, passed to the `format`
42
+ * prop.
43
+ * @param {Date} d
44
+ * @return {String}
45
+ */
46
+ export function defaultFormat(d) {
47
+ if (isDate(d)) {
48
+ const year = d.getFullYear();
49
+ const month = `${d.getMonth() + 1}`;
50
+ const day = `${d.getDate()}`;
51
+ return `${year}-${month}-${day}`;
52
+ }
53
+ return '';
54
+ }
55
+
56
+ /**
57
+ * The default function used to parse a String as Date, passed to the `parse`
58
+ * prop.
59
+ * @param {String} str
60
+ * @return {Date}
61
+ */
62
+ export function defaultParse(str) {
63
+ if (typeof str !== 'string') {
64
+ return undefined;
65
+ }
66
+ const split = str.split('-');
67
+ if (split.length !== 3) {
68
+ return undefined;
69
+ }
70
+ const year = parseInt(split[0], 10);
71
+ const month = parseInt(split[1], 10) - 1;
72
+ const day = parseInt(split[2], 10);
73
+ if (
74
+ isNaN(year) ||
75
+ isNaN(month) ||
76
+ isNaN(day) ||
77
+ day <= 0 ||
78
+ day > 31 ||
79
+ month < 0 ||
80
+ month >= 12
81
+ ) {
82
+ return undefined;
83
+ }
84
+
85
+ return new Date(year, month, day);
86
+ }
87
+
88
+ export default class DayPickerInput extends React.Component {
89
+ static propTypes = {
90
+ value: PropTypes.oneOfType([PropTypes.string, PropTypes.instanceOf(Date)]),
91
+ inputProps: PropTypes.object,
92
+ placeholder: PropTypes.string,
93
+
94
+ format: PropTypes.oneOfType([
95
+ PropTypes.string,
96
+ PropTypes.arrayOf(PropTypes.string),
97
+ ]),
98
+
99
+ formatDate: PropTypes.func,
100
+ parseDate: PropTypes.func,
101
+ parseOnInputBlur: PropTypes.bool,
102
+
103
+ overlayProps: PropTypes.object,
104
+ showOverlay: PropTypes.bool,
105
+ dayPickerProps: PropTypes.object,
106
+ hideOnDayClick: PropTypes.bool,
107
+ clickUnselectsDay: PropTypes.bool,
108
+ keepFocus: PropTypes.bool,
109
+ component: PropTypes.any,
110
+ overlayComponent: PropTypes.any,
111
+
112
+ classNames: PropTypes.shape({
113
+ container: PropTypes.string,
114
+ overlayWrapper: PropTypes.string,
115
+ overlay: PropTypes.string.isRequired,
116
+ }),
117
+
118
+ onDayChange: PropTypes.func,
119
+ onChange: PropTypes.func,
120
+ onClick: PropTypes.func,
121
+ onFocus: PropTypes.func,
122
+ onBlur: PropTypes.func,
123
+ onKeyUp: PropTypes.func,
124
+ };
125
+
126
+ static defaultProps = {
127
+ dayPickerProps: {},
128
+ value: '',
129
+ placeholder: 'YYYY-M-D',
130
+ format: 'L',
131
+ formatDate: defaultFormat,
132
+ parseDate: defaultParse,
133
+ parseOnInputBlur: false,
134
+ overlayProps: {},
135
+ showOverlay: false,
136
+ hideOnDayClick: true,
137
+ clickUnselectsDay: false,
138
+ keepFocus: true,
139
+ component: 'input',
140
+ inputProps: {},
141
+ overlayComponent: OverlayComponent,
142
+ classNames: {
143
+ container: 'DayPickerInput',
144
+ overlayWrapper: 'DayPickerInput-OverlayWrapper',
145
+ overlay: 'DayPickerInput-Overlay',
146
+ },
147
+ };
148
+
149
+ constructor(props) {
150
+ super(props);
151
+
152
+ this.state = this.getInitialStateFromProps(props);
153
+ this.state.showOverlay = props.showOverlay;
154
+
155
+ this.hideAfterDayClick = this.hideAfterDayClick.bind(this);
156
+ this.handleInputClick = this.handleInputClick.bind(this);
157
+ this.handleInputFocus = this.handleInputFocus.bind(this);
158
+ this.handleInputBlur = this.handleInputBlur.bind(this);
159
+ this.handleInputChange = this.handleInputChange.bind(this);
160
+ this.handleInputKeyDown = this.handleInputKeyDown.bind(this);
161
+ this.handleInputKeyUp = this.handleInputKeyUp.bind(this);
162
+ this.handleDayClick = this.handleDayClick.bind(this);
163
+ this.handleMonthChange = this.handleMonthChange.bind(this);
164
+ this.handleOverlayFocus = this.handleOverlayFocus.bind(this);
165
+ this.handleOverlayBlur = this.handleOverlayBlur.bind(this);
166
+ }
167
+
168
+ componentDidUpdate(prevProps) {
169
+ const newState = {};
170
+
171
+ // Current props
172
+ const {value, formatDate, format, dayPickerProps} = this.props;
173
+
174
+ // Update the input value if the `value` prop has changed
175
+ if (value !== prevProps.value) {
176
+ if (isDate(value)) {
177
+ newState.inputValue = formatDate(value, format, dayPickerProps.locale);
178
+ newState.value = formatDate(value, format, dayPickerProps.locale);
179
+ } else {
180
+ newState.inputValue = value;
181
+ newState.value = value;
182
+ }
183
+ }
184
+
185
+ // Update the month if the months from props changed
186
+ const prevMonth = prevProps.dayPickerProps.month;
187
+ if (
188
+ dayPickerProps.month &&
189
+ dayPickerProps.month !== prevMonth &&
190
+ !isSameMonth(dayPickerProps.month, prevMonth)
191
+ ) {
192
+ newState.month = dayPickerProps.month;
193
+ }
194
+
195
+ // Updated the selected days from props if they changed
196
+ if (prevProps.dayPickerProps.selectedDays !== dayPickerProps.selectedDays) {
197
+ newState.selectedDays = dayPickerProps.selectedDays;
198
+ }
199
+
200
+ if (Object.keys(newState).length > 0) {
201
+ // eslint-disable-next-line react/no-did-update-set-state
202
+ this.setState(newState);
203
+ }
204
+ }
205
+
206
+ componentWillUnmount() {
207
+ clearTimeout(this.clickTimeout);
208
+ clearTimeout(this.hideTimeout);
209
+ clearTimeout(this.inputFocusTimeout);
210
+ clearTimeout(this.inputBlurTimeout);
211
+ clearTimeout(this.overlayBlurTimeout);
212
+ }
213
+
214
+ getInitialMonthFromProps(props) {
215
+ const {dayPickerProps, format} = props;
216
+ let day;
217
+ if (props.value) {
218
+ if (isDate(props.value)) {
219
+ day = props.value;
220
+ } else {
221
+ day = props.parseDate(props.value, format, dayPickerProps.locale);
222
+ }
223
+ }
224
+ return (
225
+ dayPickerProps.initialMonth || dayPickerProps.month || day || new Date()
226
+ );
227
+ }
228
+
229
+ getInitialStateFromProps(props) {
230
+ const {dayPickerProps, formatDate, format} = props;
231
+ let {value} = props;
232
+ if (props.value && isDate(props.value)) {
233
+ value = formatDate(props.value, format, dayPickerProps.locale);
234
+ }
235
+ return {
236
+ value,
237
+ inputValue: value,
238
+ month: this.getInitialMonthFromProps(props),
239
+ selectedDays: dayPickerProps.selectedDays,
240
+ };
241
+ }
242
+
243
+ getInput() {
244
+ return this.input;
245
+ }
246
+
247
+ getDayPicker() {
248
+ return this.daypicker;
249
+ }
250
+
251
+ input = null;
252
+ daypicker = null;
253
+ clickTimeout = null;
254
+ hideTimeout = null;
255
+ inputBlurTimeout = null;
256
+ inputFocusTimeout = null;
257
+
258
+ /**
259
+ * Update the component's state and fire the `onDayChange` event passing the
260
+ * day's modifiers to it.
261
+ *
262
+ * @param {Date} day - Will be used for changing the month
263
+ * @param {String} value - Input field value
264
+ * @private
265
+ */
266
+ updateState(day, value, callback) {
267
+ const {dayPickerProps, onDayChange} = this.props;
268
+ this.setState({month: day, value, inputValue: value}, () => {
269
+ if (callback) {
270
+ callback();
271
+ }
272
+ if (!onDayChange) {
273
+ return;
274
+ }
275
+ const modifiersObj = {
276
+ disabled: dayPickerProps.disabledDays,
277
+ selected: dayPickerProps.selectedDays,
278
+ ...dayPickerProps.modifiers,
279
+ };
280
+ const modifiers = getModifiersForDay(day, modifiersObj).reduce(
281
+ (obj, modifier) => {
282
+ const newObj = {...obj};
283
+ newObj[modifier] = true;
284
+ return newObj;
285
+ },
286
+ {}
287
+ );
288
+ onDayChange(day, modifiers);
289
+ });
290
+ }
291
+
292
+ /**
293
+ * Show the Day Picker overlay.
294
+ *
295
+ * @memberof DayPickerInput
296
+ */
297
+ showDayPicker() {
298
+ const {parseDate, format, dayPickerProps} = this.props;
299
+ const {value, showOverlay} = this.state;
300
+ if (showOverlay) {
301
+ return;
302
+ }
303
+ // Reset the current displayed month when showing the overlay
304
+ const month = value
305
+ ? parseDate(value, format, dayPickerProps.locale) // Use the month in the input field
306
+ : this.getInitialMonthFromProps(this.props); // Restore the month from the props
307
+ this.setState({
308
+ showOverlay: true,
309
+ month: month || this.state.month,
310
+ });
311
+ }
312
+
313
+ /**
314
+ * Hide the Day Picker overlay
315
+ *
316
+ * @memberof DayPickerInput
317
+ */
318
+ hideDayPicker() {
319
+ if (this.state.showOverlay === false) {
320
+ return;
321
+ }
322
+ this.setState({showOverlay: false});
323
+ }
324
+
325
+ hideAfterDayClick() {
326
+ if (!this.props.hideOnDayClick) {
327
+ return;
328
+ }
329
+ this.hideTimeout = setTimeout(() => this.hideDayPicker(), HIDE_TIMEOUT);
330
+ }
331
+
332
+ handleInputClick(e) {
333
+ this.showDayPicker();
334
+ if (this.props.inputProps.onClick) {
335
+ e.persist();
336
+ this.props.inputProps.onClick(e);
337
+ }
338
+ }
339
+
340
+ handleInputFocus(e) {
341
+ this.showDayPicker();
342
+ // Set `overlayHasFocus` after a timeout so the overlay can be hidden when
343
+ // the input is blurred
344
+ this.inputFocusTimeout = setTimeout(() => {
345
+ this.overlayHasFocus = false;
346
+ }, 2);
347
+ if (this.props.inputProps.onFocus) {
348
+ e.persist();
349
+ this.props.inputProps.onFocus(e);
350
+ }
351
+ }
352
+
353
+ // When the input is blurred, the overlay should disappear. However the input
354
+ // is blurred also when the user interacts with the overlay (e.g. the overlay
355
+ // get the focus by clicking it). In these cases, the overlay should not be
356
+ // hidden. There are different approaches to avoid hiding the overlay when
357
+ // this happens, but the only cross-browser hack we’ve found is to set all
358
+ // these timeouts in code before changing `overlayHasFocus`.
359
+ handleInputBlur(e) {
360
+ const {
361
+ dayPickerProps,
362
+ format,
363
+ inputProps,
364
+ onDayChange,
365
+ parseDate,
366
+ parseOnInputBlur,
367
+ } = this.props;
368
+ const {value, inputValue} = this.state;
369
+
370
+ this.inputBlurTimeout = setTimeout(() => {
371
+ if (!this.overlayHasFocus) {
372
+ this.hideDayPicker();
373
+ }
374
+ }, 1);
375
+ if (inputProps.onBlur) {
376
+ e.persist();
377
+ inputProps.onBlur(e);
378
+ }
379
+
380
+ if (parseOnInputBlur) {
381
+ const newValue = e.target.value;
382
+ if (value && newValue.trim() === '') {
383
+ this.setState({value: newValue});
384
+ if (onDayChange) {
385
+ onDayChange(undefined, {});
386
+ }
387
+ return;
388
+ }
389
+ const newDay = parseDate(newValue, format, dayPickerProps.locale) || '';
390
+ const currentValue =
391
+ parseDate(value, format, dayPickerProps.locale) || '';
392
+ if (isNaN(newDay)) {
393
+ this.setState({inputValue: value});
394
+ return;
395
+ }
396
+ if (newDay.valueOf() === currentValue.valueOf() && inputValue === value) {
397
+ return;
398
+ }
399
+ this.updateState(newDay, newValue);
400
+ }
401
+ }
402
+
403
+ handleOverlayFocus(e) {
404
+ e.preventDefault();
405
+ this.overlayHasFocus = true;
406
+ if (!this.props.keepFocus) {
407
+ return;
408
+ }
409
+ this.input.focus();
410
+ }
411
+
412
+ handleOverlayBlur() {
413
+ // We need to set a timeout otherwise IE11 will hide the overlay when
414
+ // focusing it
415
+ this.overlayBlurTimeout = setTimeout(() => {
416
+ this.overlayHasFocus = false;
417
+ }, 3);
418
+ }
419
+
420
+ handleInputChange(e) {
421
+ const {
422
+ dayPickerProps,
423
+ format,
424
+ inputProps,
425
+ onDayChange,
426
+ parseDate,
427
+ parseOnInputBlur,
428
+ } = this.props;
429
+ if (inputProps.onChange) {
430
+ e.persist();
431
+ inputProps.onChange(e);
432
+ }
433
+
434
+ if (parseOnInputBlur) {
435
+ this.setState({inputValue: e.target.value});
436
+ return;
437
+ }
438
+
439
+ const {value} = e.target;
440
+ if (value.trim() === '') {
441
+ this.setState({value, inputValue: value});
442
+ if (onDayChange) onDayChange(undefined, {});
443
+ return;
444
+ }
445
+ const day = parseDate(value, format, dayPickerProps.locale);
446
+ if (!day) {
447
+ this.setState({value, inputValue: value});
448
+ if (onDayChange) onDayChange(undefined, {});
449
+ return;
450
+ }
451
+ this.updateState(day, value);
452
+ }
453
+
454
+ handleInputKeyDown(e) {
455
+ if (e.keyCode === TAB) {
456
+ this.hideDayPicker();
457
+ } else {
458
+ this.showDayPicker();
459
+ }
460
+ if (this.props.inputProps.onKeyDown) {
461
+ e.persist();
462
+ this.props.inputProps.onKeyDown(e);
463
+ }
464
+ }
465
+
466
+ handleInputKeyUp(e) {
467
+ if (e.keyCode === ESC) {
468
+ this.hideDayPicker();
469
+ } else {
470
+ this.showDayPicker();
471
+ }
472
+ if (this.props.inputProps.onKeyUp) {
473
+ e.persist();
474
+ this.props.inputProps.onKeyUp(e);
475
+ }
476
+ }
477
+
478
+ handleMonthChange(month) {
479
+ this.setState({month}, () => {
480
+ if (
481
+ this.props.dayPickerProps &&
482
+ this.props.dayPickerProps.onMonthChange
483
+ ) {
484
+ this.props.dayPickerProps.onMonthChange(month);
485
+ }
486
+ });
487
+ }
488
+
489
+ handleDayClick(day, modifiers, e) {
490
+ const {
491
+ clickUnselectsDay,
492
+ dayPickerProps,
493
+ onDayChange,
494
+ formatDate,
495
+ format,
496
+ } = this.props;
497
+ if (dayPickerProps.onDayClick) {
498
+ dayPickerProps.onDayClick(day, modifiers, e);
499
+ }
500
+
501
+ // Do nothing if the day is disabled
502
+ if (modifiers.disabled) {
503
+ return;
504
+ }
505
+
506
+ // If the clicked day is already selected, remove the clicked day
507
+ // from the selected days and empty the field value
508
+ if (modifiers.selected && clickUnselectsDay) {
509
+ let {selectedDays} = this.state;
510
+ if (Array.isArray(selectedDays)) {
511
+ selectedDays = selectedDays.slice(0);
512
+ const selectedDayIdx = selectedDays.indexOf(day);
513
+ selectedDays.splice(selectedDayIdx, 1);
514
+ } else if (selectedDays) {
515
+ selectedDays = null;
516
+ }
517
+ this.setState(
518
+ {value: '', inputValue: '', selectedDays},
519
+ this.hideAfterDayClick
520
+ );
521
+ if (onDayChange) {
522
+ onDayChange(undefined, modifiers);
523
+ }
524
+ return;
525
+ }
526
+
527
+ const value = formatDate(day, format, dayPickerProps.locale);
528
+ this.setState({value, inputValue: value, month: day}, () => {
529
+ if (onDayChange) {
530
+ onDayChange(day, modifiers);
531
+ }
532
+ this.hideAfterDayClick();
533
+ });
534
+ }
535
+
536
+ renderOverlay() {
537
+ const {
538
+ classNames,
539
+ dayPickerProps,
540
+ parseDate,
541
+ formatDate,
542
+ format,
543
+ overlayProps,
544
+ } = this.props;
545
+ const {selectedDays, value} = this.state;
546
+ let selectedDay;
547
+ if (!selectedDays && value) {
548
+ const day = parseDate(value, format, dayPickerProps.locale);
549
+ if (day) {
550
+ selectedDay = day;
551
+ }
552
+ } else if (selectedDays) {
553
+ selectedDay = selectedDays;
554
+ }
555
+ let onTodayButtonClick;
556
+ if (dayPickerProps.todayButton) {
557
+ // Set the current day when clicking the today button
558
+ onTodayButtonClick = () =>
559
+ this.updateState(
560
+ new Date(),
561
+ formatDate(new Date(), format, dayPickerProps.locale),
562
+ this.hideAfterDayClick
563
+ );
564
+ }
565
+ const Overlay = this.props.overlayComponent;
566
+ return (
567
+ <Overlay
568
+ classNames={classNames}
569
+ month={this.state.month}
570
+ selectedDay={selectedDay}
571
+ input={this.input}
572
+ tabIndex={0} // tabIndex is necessary to catch focus/blur events on Safari
573
+ onFocus={this.handleOverlayFocus}
574
+ onBlur={this.handleOverlayBlur}
575
+ {...overlayProps}
576
+ >
577
+ <DayPicker
578
+ ref={(el) => (this.daypicker = el)}
579
+ onTodayButtonClick={onTodayButtonClick}
580
+ {...dayPickerProps}
581
+ month={this.state.month}
582
+ selectedDays={selectedDay}
583
+ onDayClick={this.handleDayClick}
584
+ onMonthChange={this.handleMonthChange}
585
+ />
586
+ </Overlay>
587
+ );
588
+ }
589
+
590
+ render() {
591
+ const Input = this.props.component;
592
+ const {inputProps} = this.props;
593
+ return (
594
+ <div className={this.props.classNames.container}>
595
+ <Input
596
+ ref={(el) => (this.input = el)}
597
+ placeholder={this.props.placeholder}
598
+ {...inputProps}
599
+ value={this.state.inputValue}
600
+ onChange={this.handleInputChange}
601
+ onFocus={this.handleInputFocus}
602
+ onBlur={this.handleInputBlur}
603
+ onKeyDown={this.handleInputKeyDown}
604
+ onKeyUp={this.handleInputKeyUp}
605
+ onClick={!inputProps.disabled ? this.handleInputClick : undefined}
606
+ />
607
+ {this.state.showOverlay && this.renderOverlay()}
608
+ </div>
609
+ );
610
+ }
611
+ }