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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

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
+ }