cx 24.5.2 → 24.6.2

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.
@@ -1,527 +1,527 @@
1
- import { Widget, VDOM } from "../../ui/Widget";
2
- import { Field, getFieldTooltip } from "./Field";
3
- import { Culture } from "../../ui/Culture";
4
- import { FocusManager, oneFocusOut, offFocusOut } from "../../ui/FocusManager";
5
- import { StringTemplate } from "../../data/StringTemplate";
6
- import { zeroTime } from "../../util/date/zeroTime";
7
- import { dateDiff } from "../../util/date/dateDiff";
8
- import { lowerBoundCheck } from "../../util/date/lowerBoundCheck";
9
- import { upperBoundCheck } from "../../util/date/upperBoundCheck";
10
- import { sameDate } from "../../util/date/sameDate";
11
- import {
12
- tooltipParentWillReceiveProps,
13
- tooltipParentWillUnmount,
14
- tooltipMouseMove,
15
- tooltipMouseLeave,
16
- tooltipParentDidMount,
17
- } from "../overlay/tooltip-ops";
18
- import { KeyCode } from "../../util/KeyCode";
19
- import { Localization } from "../../ui/Localization";
20
- import ForwardIcon from "../icons/forward";
21
- import DropdownIcon from "../icons/drop-down";
22
- import "../../ui/Format";
23
- import { monthStart } from "../../util/date/monthStart";
24
-
25
- export class Calendar extends Field {
26
- declareData() {
27
- super.declareData(
28
- {
29
- value: undefined,
30
- refDate: undefined,
31
- disabled: undefined,
32
- enabled: undefined,
33
- minValue: undefined,
34
- minExclusive: undefined,
35
- maxValue: undefined,
36
- maxExclusive: undefined,
37
- focusable: undefined,
38
- dayData: undefined,
39
- },
40
- ...arguments
41
- );
42
- }
43
-
44
- init() {
45
- if (this.unfocusable) this.focusable = false;
46
-
47
- super.init();
48
- }
49
-
50
- prepareData(context, { data }) {
51
- data.stateMods = {
52
- disabled: data.disabled,
53
- };
54
-
55
- if (data.value) {
56
- let d = new Date(data.value);
57
- if (!isNaN(d.getTime())) {
58
- data.date = zeroTime(d);
59
- }
60
- }
61
-
62
- if (data.refDate) data.refDate = zeroTime(new Date(data.refDate));
63
-
64
- if (data.maxValue) data.maxValue = zeroTime(new Date(data.maxValue));
65
-
66
- if (data.minValue) data.minValue = zeroTime(new Date(data.minValue));
67
-
68
- super.prepareData(...arguments);
69
- }
70
-
71
- validate(context, instance) {
72
- super.validate(context, instance);
73
- let { data, widget } = instance;
74
- if (!data.error && data.date) {
75
- let d;
76
- if (data.maxValue) {
77
- d = dateDiff(data.date, data.maxValue);
78
- if (d > 0) data.error = StringTemplate.format(this.maxValueErrorText, data.maxValue);
79
- else if (d == 0 && data.maxExclusive)
80
- data.error = StringTemplate.format(this.maxExclusiveErrorText, data.maxValue);
81
- }
82
-
83
- if (data.minValue) {
84
- d = dateDiff(data.date, data.minValue);
85
- if (d < 0) data.error = StringTemplate.format(this.minValueErrorText, data.minValue);
86
- else if (d == 0 && data.minExclusive)
87
- data.error = StringTemplate.format(this.minExclusiveErrorText, data.minValue);
88
- }
89
-
90
- if (widget.disabledDaysOfWeek) {
91
- if (widget.disabledDaysOfWeek.includes(data.date.getDay())) data.error = this.disabledDaysOfWeekErrorText;
92
- }
93
-
94
- if (data.dayData) {
95
- let date = new Date(data.value);
96
- let info = data.dayData[date.toDateString()];
97
- if (info && info.disabled) data.error = this.disabledDaysOfWeekErrorText;
98
- }
99
- }
100
- }
101
-
102
- renderInput(context, instance, key) {
103
- return (
104
- <CalendarCmp key={key} instance={instance} handleSelect={(e, date) => this.handleSelect(e, instance, date)} />
105
- );
106
- }
107
-
108
- handleSelect(e, instance, date) {
109
- let { store, data, widget } = instance;
110
-
111
- e.stopPropagation();
112
-
113
- if (data.disabled) return;
114
-
115
- if (!validationCheck(date, data)) return;
116
-
117
- if (this.onBeforeSelect && instance.invoke("onBeforeSelect", e, instance, date) === false) return;
118
-
119
- if (widget.partial) {
120
- let mixed = new Date(data.value);
121
- if (data.value && !isNaN(mixed)) {
122
- mixed.setFullYear(date.getFullYear());
123
- mixed.setMonth(date.getMonth());
124
- mixed.setDate(date.getDate());
125
- date = mixed;
126
- }
127
- }
128
-
129
- let encode = widget.encoding || Culture.getDefaultDateEncoding();
130
- instance.set("value", encode(date));
131
-
132
- if (this.onSelect) instance.invoke("onSelect", e, instance, date);
133
- }
134
- }
135
-
136
- Calendar.prototype.baseClass = "calendar";
137
- Calendar.prototype.highlightToday = true;
138
- Calendar.prototype.maxValueErrorText = "Select a date not after {0:d}.";
139
- Calendar.prototype.maxExclusiveErrorText = "Select a date before {0:d}.";
140
- Calendar.prototype.minValueErrorText = "Select a date not before {0:d}.";
141
- Calendar.prototype.minExclusiveErrorText = "Select a date after {0:d}.";
142
- Calendar.prototype.disabledDaysOfWeekErrorText = "Selected day of week is not allowed.";
143
- Calendar.prototype.suppressErrorsUntilVisited = false;
144
- Calendar.prototype.showTodayButton = false;
145
- Calendar.prototype.todayButtonText = "Today";
146
- Calendar.prototype.startWithMonday = false;
147
- Calendar.prototype.focusable = true;
148
-
149
- Localization.registerPrototype("cx/widgets/Calendar", Calendar);
150
-
151
- const validationCheck = (date, data, disabledDaysOfWeek) => {
152
- if (data.maxValue && !upperBoundCheck(date, data.maxValue, data.maxExclusive)) return false;
153
-
154
- if (data.minValue && !lowerBoundCheck(date, data.minValue, data.minExclusive)) return false;
155
-
156
- if (disabledDaysOfWeek && disabledDaysOfWeek.includes(date.getDay())) return false;
157
-
158
- if (data.dayData) {
159
- let day = data.dayData[date.toDateString()];
160
- if (day && (day.disabled || day.unselectable)) return false;
161
- }
162
-
163
- return true;
164
- };
165
-
166
- export class CalendarCmp extends VDOM.Component {
167
- constructor(props) {
168
- super(props);
169
- let { data } = props.instance;
170
-
171
- let refDate = data.refDate ? data.refDate : data.date || zeroTime(new Date());
172
-
173
- this.state = Object.assign(
174
- {
175
- hover: false,
176
- focus: false,
177
- cursor: zeroTime(data.date || refDate),
178
- },
179
- this.getPage(refDate)
180
- );
181
-
182
- this.handleMouseMove = this.handleMouseMove.bind(this);
183
- this.handleMouseDown = this.handleMouseDown.bind(this);
184
- }
185
-
186
- getPage(refDate) {
187
- refDate = monthStart(refDate); //make a copy
188
-
189
- let startWithMonday = this.props.instance.widget.startWithMonday;
190
-
191
- let startDay = startWithMonday ? 1 : 0;
192
- let startDate = new Date(refDate);
193
- while (startDate.getDay() != startDay) startDate.setDate(startDate.getDate() - 1);
194
-
195
- let endDate = new Date(refDate);
196
- endDate.setMonth(refDate.getMonth() + 1);
197
- endDate.setDate(endDate.getDate() - 1);
198
-
199
- let endDay = startWithMonday ? 0 : 6;
200
- while (endDate.getDay() != endDay) endDate.setDate(endDate.getDate() + 1);
201
-
202
- return {
203
- refDate,
204
- startDate,
205
- endDate,
206
- };
207
- }
208
-
209
- moveCursor(e, date, options = {}) {
210
- e.preventDefault();
211
- e.stopPropagation();
212
-
213
- date = zeroTime(date);
214
- if (date.getTime() == this.state.cursor.getTime()) return;
215
-
216
- let refDate = this.state.refDate;
217
-
218
- if (options.movePage || date < this.state.startDate || date > this.state.endDate) refDate = date;
219
-
220
- this.setState({
221
- ...this.getPage(refDate),
222
- cursor: date,
223
- });
224
- }
225
-
226
- move(e, period, delta) {
227
- e.preventDefault();
228
- e.stopPropagation();
229
-
230
- let refDate = this.state.refDate;
231
-
232
- switch (period) {
233
- case "y":
234
- refDate.setFullYear(refDate.getFullYear() + delta);
235
- break;
236
-
237
- case "m":
238
- refDate.setMonth(refDate.getMonth() + delta);
239
- break;
240
- }
241
-
242
- let page = this.getPage(refDate);
243
- if (this.state.cursor < page.startDate) page.cursor = page.startDate;
244
- else if (this.state.cursor > page.endDate) page.cursor = page.endDate;
245
-
246
- this.setState(page);
247
- }
248
-
249
- handleKeyPress(e) {
250
- let cursor = new Date(this.state.cursor);
251
-
252
- switch (e.keyCode) {
253
- case KeyCode.enter:
254
- this.props.handleSelect(e, this.state.cursor);
255
- break;
256
-
257
- case KeyCode.left:
258
- cursor.setDate(cursor.getDate() - 1);
259
- this.moveCursor(e, cursor);
260
- break;
261
-
262
- case KeyCode.right:
263
- cursor.setDate(cursor.getDate() + 1);
264
- this.moveCursor(e, cursor);
265
- break;
266
-
267
- case KeyCode.up:
268
- cursor.setDate(cursor.getDate() - 7);
269
- this.moveCursor(e, cursor);
270
- break;
271
-
272
- case KeyCode.down:
273
- cursor.setDate(cursor.getDate() + 7);
274
- this.moveCursor(e, cursor);
275
- break;
276
-
277
- case KeyCode.pageUp:
278
- cursor.setMonth(cursor.getMonth() - 1);
279
- this.moveCursor(e, cursor, { movePage: true });
280
- break;
281
-
282
- case KeyCode.pageDown:
283
- cursor.setMonth(cursor.getMonth() + 1);
284
- this.moveCursor(e, cursor, { movePage: true });
285
- break;
286
-
287
- case KeyCode.home:
288
- cursor.setDate(1);
289
- this.moveCursor(e, cursor, { movePage: true });
290
- break;
291
-
292
- case KeyCode.end:
293
- cursor.setMonth(cursor.getMonth() + 1);
294
- cursor.setDate(0);
295
- this.moveCursor(e, cursor, { movePage: true });
296
- break;
297
-
298
- default:
299
- let { instance } = this.props;
300
- let { widget } = instance;
301
- if (widget.onKeyDown) instance.invoke("onKeyDown", e, instance);
302
- break;
303
- }
304
- }
305
-
306
- handleWheel(e) {
307
- e.preventDefault();
308
- e.stopPropagation();
309
-
310
- let cursor = new Date(this.state.cursor);
311
-
312
- if (e.deltaY < 0) {
313
- cursor.setMonth(cursor.getMonth() - 1);
314
- this.moveCursor(e, cursor, { movePage: true });
315
- } else if (e.deltaY > 0) {
316
- cursor.setMonth(cursor.getMonth() + 1);
317
- this.moveCursor(e, cursor, { movePage: true });
318
- }
319
- }
320
-
321
- handleBlur(e) {
322
- FocusManager.nudge();
323
- let { instance } = this.props;
324
- let { widget } = instance;
325
- if (widget.onBlur) instance.invoke("onBlur", e, instance);
326
- this.setState({
327
- focus: false,
328
- });
329
- }
330
-
331
- handleFocus(e) {
332
- oneFocusOut(this, this.el, this.handleFocusOut.bind(this));
333
- this.setState({
334
- focus: true,
335
- });
336
- }
337
-
338
- handleFocusOut() {
339
- let { instance } = this.props;
340
- let { widget } = instance;
341
- if (widget.onFocusOut) instance.invoke("onFocusOut", null, instance);
342
- }
343
-
344
- handleMouseLeave(e) {
345
- tooltipMouseLeave(e, ...getFieldTooltip(this.props.instance));
346
- this.setState({
347
- hover: false,
348
- });
349
- }
350
-
351
- handleMouseEnter(e) {
352
- this.setState({
353
- hover: true,
354
- });
355
- }
356
-
357
- handleMouseMove(e) {
358
- this.moveCursor(e, readDate(e.target.dataset));
359
- }
360
-
361
- handleMouseDown(e) {
362
- this.props.handleSelect(e, readDate(e.target.dataset));
363
- }
364
-
365
- componentDidMount() {
366
- //calendar doesn't bring up keyboard so it's ok to focus it even on mobile
367
- if (this.props.instance.widget.autoFocus) this.el.focus();
368
-
369
- tooltipParentDidMount(this.el, ...getFieldTooltip(this.props.instance));
370
- }
371
-
372
- UNSAFE_componentWillReceiveProps(props) {
373
- let { data } = props.instance;
374
- if (data.date)
375
- this.setState({
376
- ...this.getPage(data.date),
377
- value: data.date,
378
- });
379
-
380
- tooltipParentWillReceiveProps(this.el, ...getFieldTooltip(props.instance));
381
- }
382
-
383
- componentWillUnmount() {
384
- offFocusOut(this);
385
- tooltipParentWillUnmount(this.props.instance);
386
- }
387
-
388
- render() {
389
- let { data, widget } = this.props.instance;
390
- let { CSS, baseClass, disabledDaysOfWeek, startWithMonday } = widget;
391
-
392
- let { refDate, startDate, endDate } = this.getPage(this.state.refDate);
393
-
394
- let month = refDate.getMonth();
395
- let year = refDate.getFullYear();
396
- let weeks = [];
397
- let date = startDate;
398
-
399
- let empty = {};
400
-
401
- let today = zeroTime(new Date());
402
- while (date >= startDate && date <= endDate) {
403
- let days = [];
404
- for (let i = 0; i < 7; i++) {
405
- let dayInfo = (data.dayData && data.dayData[date.toDateString()]) || empty;
406
- let unselectable = !validationCheck(date, data, disabledDaysOfWeek);
407
- let classNames = CSS.expand(
408
- CSS.element(baseClass, "day", {
409
- outside: month != date.getMonth(),
410
- unselectable: unselectable,
411
- selected: data.date && sameDate(data.date, date),
412
- cursor:
413
- (this.state.hover || this.state.focus) && this.state.cursor && sameDate(this.state.cursor, date),
414
- today: widget.highlightToday && sameDate(date, today),
415
- }),
416
- dayInfo.className,
417
- CSS.mod(dayInfo.mod)
418
- );
419
- let dateInst = new Date(date);
420
- days.push(
421
- <td
422
- key={i}
423
- className={classNames}
424
- style={CSS.parseStyle(dayInfo.style)}
425
- data-year={dateInst.getFullYear()}
426
- data-month={dateInst.getMonth() + 1}
427
- data-date={dateInst.getDate()}
428
- onMouseMove={unselectable ? null : this.handleMouseMove}
429
- onMouseDown={unselectable ? null : this.handleMouseDown}
430
- >
431
- {date.getDate()}
432
- </td>
433
- );
434
- date.setDate(date.getDate() + 1);
435
- }
436
- weeks.push(
437
- <tr key={weeks.length} className={CSS.element(baseClass, "week")}>
438
- <td />
439
- {days}
440
- <td />
441
- </tr>
442
- );
443
- }
444
-
445
- let culture = Culture.getDateTimeCulture();
446
- let monthNames = culture.getMonthNames("long");
447
- let dayNames = culture.getWeekdayNames("short").map((x) => x.substr(0, 2));
448
- if (startWithMonday) dayNames = [...dayNames.slice(1), dayNames[0]];
449
-
450
- return (
451
- <div
452
- className={data.classNames}
453
- tabIndex={data.disabled || !data.focusable ? null : data.tabIndex || 0}
454
- onKeyDown={(e) => this.handleKeyPress(e)}
455
- onMouseDown={(e) => {
456
- // prevent losing focus from the input field
457
- if (!data.focusable) {
458
- e.preventDefault();
459
- }
460
- e.stopPropagation();
461
- }}
462
- ref={(el) => {
463
- this.el = el;
464
- }}
465
- onMouseMove={(e) => tooltipMouseMove(e, ...getFieldTooltip(this.props.instance))}
466
- onMouseLeave={(e) => this.handleMouseLeave(e)}
467
- onMouseEnter={(e) => this.handleMouseEnter(e)}
468
- onWheel={(e) => this.handleWheel(e)}
469
- onFocus={(e) => this.handleFocus(e)}
470
- onBlur={(e) => this.handleBlur(e)}
471
- >
472
- <table>
473
- <thead>
474
- <tr key="h" className={CSS.element(baseClass, "header")}>
475
- <td />
476
- <td onClick={(e) => this.move(e, "y", -1)}>
477
- <ForwardIcon className={CSS.element(baseClass, "icon-prev-year")} />
478
- </td>
479
- <td onClick={(e) => this.move(e, "m", -1)}>
480
- <DropdownIcon className={CSS.element(baseClass, "icon-prev-month")} />
481
- </td>
482
- <th className={CSS.element(baseClass, "display")} colSpan="3">
483
- {monthNames[month]}
484
- <br />
485
- {year}
486
- </th>
487
- <td onClick={(e) => this.move(e, "m", +1)}>
488
- <DropdownIcon className={CSS.element(baseClass, "icon-next-month")} />
489
- </td>
490
- <td onClick={(e) => this.move(e, "y", +1)}>
491
- <ForwardIcon className={CSS.element(baseClass, "icon-next-year")} />
492
- </td>
493
- <td />
494
- </tr>
495
- <tr key="d" className={CSS.element(baseClass, "day-names")}>
496
- <td />
497
- {dayNames.map((name, i) => (
498
- <th key={i}>{name}</th>
499
- ))}
500
- <td />
501
- </tr>
502
- </thead>
503
- <tbody>{weeks}</tbody>
504
- </table>
505
- {widget.showTodayButton && (
506
- <div className={CSS.element(baseClass, "toolbar")}>
507
- <button
508
- className={CSS.expand(CSS.element(baseClass, "today-button"), CSS.block("button", "hollow"))}
509
- data-year={today.getFullYear()}
510
- data-month={today.getMonth() + 1}
511
- data-date={today.getDate()}
512
- onClick={(e) => {
513
- this.handleMouseDown(e);
514
- }}
515
- >
516
- {widget.todayButtonText}
517
- </button>
518
- </div>
519
- )}
520
- </div>
521
- );
522
- }
523
- }
524
-
525
- const readDate = (ds) => new Date(Number(ds.year), Number(ds.month) - 1, Number(ds.date));
526
-
527
- Widget.alias("calendar", Calendar);
1
+ import { Widget, VDOM } from "../../ui/Widget";
2
+ import { Field, getFieldTooltip } from "./Field";
3
+ import { Culture } from "../../ui/Culture";
4
+ import { FocusManager, oneFocusOut, offFocusOut } from "../../ui/FocusManager";
5
+ import { StringTemplate } from "../../data/StringTemplate";
6
+ import { zeroTime } from "../../util/date/zeroTime";
7
+ import { dateDiff } from "../../util/date/dateDiff";
8
+ import { lowerBoundCheck } from "../../util/date/lowerBoundCheck";
9
+ import { upperBoundCheck } from "../../util/date/upperBoundCheck";
10
+ import { sameDate } from "../../util/date/sameDate";
11
+ import {
12
+ tooltipParentWillReceiveProps,
13
+ tooltipParentWillUnmount,
14
+ tooltipMouseMove,
15
+ tooltipMouseLeave,
16
+ tooltipParentDidMount,
17
+ } from "../overlay/tooltip-ops";
18
+ import { KeyCode } from "../../util/KeyCode";
19
+ import { Localization } from "../../ui/Localization";
20
+ import ForwardIcon from "../icons/forward";
21
+ import DropdownIcon from "../icons/drop-down";
22
+ import "../../ui/Format";
23
+ import { monthStart } from "../../util/date/monthStart";
24
+
25
+ export class Calendar extends Field {
26
+ declareData() {
27
+ super.declareData(
28
+ {
29
+ value: undefined,
30
+ refDate: undefined,
31
+ disabled: undefined,
32
+ enabled: undefined,
33
+ minValue: undefined,
34
+ minExclusive: undefined,
35
+ maxValue: undefined,
36
+ maxExclusive: undefined,
37
+ focusable: undefined,
38
+ dayData: undefined,
39
+ },
40
+ ...arguments
41
+ );
42
+ }
43
+
44
+ init() {
45
+ if (this.unfocusable) this.focusable = false;
46
+
47
+ super.init();
48
+ }
49
+
50
+ prepareData(context, { data }) {
51
+ data.stateMods = {
52
+ disabled: data.disabled,
53
+ };
54
+
55
+ if (data.value) {
56
+ let d = new Date(data.value);
57
+ if (!isNaN(d.getTime())) {
58
+ data.date = zeroTime(d);
59
+ }
60
+ }
61
+
62
+ if (data.refDate) data.refDate = zeroTime(new Date(data.refDate));
63
+
64
+ if (data.maxValue) data.maxValue = zeroTime(new Date(data.maxValue));
65
+
66
+ if (data.minValue) data.minValue = zeroTime(new Date(data.minValue));
67
+
68
+ super.prepareData(...arguments);
69
+ }
70
+
71
+ validate(context, instance) {
72
+ super.validate(context, instance);
73
+ let { data, widget } = instance;
74
+ if (!data.error && data.date) {
75
+ let d;
76
+ if (data.maxValue) {
77
+ d = dateDiff(data.date, data.maxValue);
78
+ if (d > 0) data.error = StringTemplate.format(this.maxValueErrorText, data.maxValue);
79
+ else if (d == 0 && data.maxExclusive)
80
+ data.error = StringTemplate.format(this.maxExclusiveErrorText, data.maxValue);
81
+ }
82
+
83
+ if (data.minValue) {
84
+ d = dateDiff(data.date, data.minValue);
85
+ if (d < 0) data.error = StringTemplate.format(this.minValueErrorText, data.minValue);
86
+ else if (d == 0 && data.minExclusive)
87
+ data.error = StringTemplate.format(this.minExclusiveErrorText, data.minValue);
88
+ }
89
+
90
+ if (widget.disabledDaysOfWeek) {
91
+ if (widget.disabledDaysOfWeek.includes(data.date.getDay())) data.error = this.disabledDaysOfWeekErrorText;
92
+ }
93
+
94
+ if (data.dayData) {
95
+ let date = new Date(data.value);
96
+ let info = data.dayData[date.toDateString()];
97
+ if (info && info.disabled) data.error = this.disabledDaysOfWeekErrorText;
98
+ }
99
+ }
100
+ }
101
+
102
+ renderInput(context, instance, key) {
103
+ return (
104
+ <CalendarCmp key={key} instance={instance} handleSelect={(e, date) => this.handleSelect(e, instance, date)} />
105
+ );
106
+ }
107
+
108
+ handleSelect(e, instance, date) {
109
+ let { store, data, widget } = instance;
110
+
111
+ e.stopPropagation();
112
+
113
+ if (data.disabled) return;
114
+
115
+ if (!validationCheck(date, data)) return;
116
+
117
+ if (this.onBeforeSelect && instance.invoke("onBeforeSelect", e, instance, date) === false) return;
118
+
119
+ if (widget.partial) {
120
+ let mixed = new Date(data.value);
121
+ if (data.value && !isNaN(mixed)) {
122
+ mixed.setFullYear(date.getFullYear());
123
+ mixed.setMonth(date.getMonth());
124
+ mixed.setDate(date.getDate());
125
+ date = mixed;
126
+ }
127
+ }
128
+
129
+ let encode = widget.encoding || Culture.getDefaultDateEncoding();
130
+ instance.set("value", encode(date));
131
+
132
+ if (this.onSelect) instance.invoke("onSelect", e, instance, date);
133
+ }
134
+ }
135
+
136
+ Calendar.prototype.baseClass = "calendar";
137
+ Calendar.prototype.highlightToday = true;
138
+ Calendar.prototype.maxValueErrorText = "Select a date not after {0:d}.";
139
+ Calendar.prototype.maxExclusiveErrorText = "Select a date before {0:d}.";
140
+ Calendar.prototype.minValueErrorText = "Select a date not before {0:d}.";
141
+ Calendar.prototype.minExclusiveErrorText = "Select a date after {0:d}.";
142
+ Calendar.prototype.disabledDaysOfWeekErrorText = "Selected day of week is not allowed.";
143
+ Calendar.prototype.suppressErrorsUntilVisited = false;
144
+ Calendar.prototype.showTodayButton = false;
145
+ Calendar.prototype.todayButtonText = "Today";
146
+ Calendar.prototype.startWithMonday = false;
147
+ Calendar.prototype.focusable = true;
148
+
149
+ Localization.registerPrototype("cx/widgets/Calendar", Calendar);
150
+
151
+ const validationCheck = (date, data, disabledDaysOfWeek) => {
152
+ if (data.maxValue && !upperBoundCheck(date, data.maxValue, data.maxExclusive)) return false;
153
+
154
+ if (data.minValue && !lowerBoundCheck(date, data.minValue, data.minExclusive)) return false;
155
+
156
+ if (disabledDaysOfWeek && disabledDaysOfWeek.includes(date.getDay())) return false;
157
+
158
+ if (data.dayData) {
159
+ let day = data.dayData[date.toDateString()];
160
+ if (day && (day.disabled || day.unselectable)) return false;
161
+ }
162
+
163
+ return true;
164
+ };
165
+
166
+ export class CalendarCmp extends VDOM.Component {
167
+ constructor(props) {
168
+ super(props);
169
+ let { data } = props.instance;
170
+
171
+ let refDate = data.refDate ? data.refDate : data.date || zeroTime(new Date());
172
+
173
+ this.state = Object.assign(
174
+ {
175
+ hover: false,
176
+ focus: false,
177
+ cursor: zeroTime(data.date || refDate),
178
+ },
179
+ this.getPage(refDate)
180
+ );
181
+
182
+ this.handleMouseMove = this.handleMouseMove.bind(this);
183
+ this.handleMouseDown = this.handleMouseDown.bind(this);
184
+ }
185
+
186
+ getPage(refDate) {
187
+ refDate = monthStart(refDate); //make a copy
188
+
189
+ let startWithMonday = this.props.instance.widget.startWithMonday;
190
+
191
+ let startDay = startWithMonday ? 1 : 0;
192
+ let startDate = new Date(refDate);
193
+ while (startDate.getDay() != startDay) startDate.setDate(startDate.getDate() - 1);
194
+
195
+ let endDate = new Date(refDate);
196
+ endDate.setMonth(refDate.getMonth() + 1);
197
+ endDate.setDate(endDate.getDate() - 1);
198
+
199
+ let endDay = startWithMonday ? 0 : 6;
200
+ while (endDate.getDay() != endDay) endDate.setDate(endDate.getDate() + 1);
201
+
202
+ return {
203
+ refDate,
204
+ startDate,
205
+ endDate,
206
+ };
207
+ }
208
+
209
+ moveCursor(e, date, options = {}) {
210
+ e.preventDefault();
211
+ e.stopPropagation();
212
+
213
+ date = zeroTime(date);
214
+ if (date.getTime() == this.state.cursor.getTime()) return;
215
+
216
+ let refDate = this.state.refDate;
217
+
218
+ if (options.movePage || date < this.state.startDate || date > this.state.endDate) refDate = date;
219
+
220
+ this.setState({
221
+ ...this.getPage(refDate),
222
+ cursor: date,
223
+ });
224
+ }
225
+
226
+ move(e, period, delta) {
227
+ e.preventDefault();
228
+ e.stopPropagation();
229
+
230
+ let refDate = this.state.refDate;
231
+
232
+ switch (period) {
233
+ case "y":
234
+ refDate.setFullYear(refDate.getFullYear() + delta);
235
+ break;
236
+
237
+ case "m":
238
+ refDate.setMonth(refDate.getMonth() + delta);
239
+ break;
240
+ }
241
+
242
+ let page = this.getPage(refDate);
243
+ if (this.state.cursor < page.startDate) page.cursor = page.startDate;
244
+ else if (this.state.cursor > page.endDate) page.cursor = page.endDate;
245
+
246
+ this.setState(page);
247
+ }
248
+
249
+ handleKeyPress(e) {
250
+ let cursor = new Date(this.state.cursor);
251
+
252
+ switch (e.keyCode) {
253
+ case KeyCode.enter:
254
+ this.props.handleSelect(e, this.state.cursor);
255
+ break;
256
+
257
+ case KeyCode.left:
258
+ cursor.setDate(cursor.getDate() - 1);
259
+ this.moveCursor(e, cursor);
260
+ break;
261
+
262
+ case KeyCode.right:
263
+ cursor.setDate(cursor.getDate() + 1);
264
+ this.moveCursor(e, cursor);
265
+ break;
266
+
267
+ case KeyCode.up:
268
+ cursor.setDate(cursor.getDate() - 7);
269
+ this.moveCursor(e, cursor);
270
+ break;
271
+
272
+ case KeyCode.down:
273
+ cursor.setDate(cursor.getDate() + 7);
274
+ this.moveCursor(e, cursor);
275
+ break;
276
+
277
+ case KeyCode.pageUp:
278
+ cursor.setMonth(cursor.getMonth() - 1);
279
+ this.moveCursor(e, cursor, { movePage: true });
280
+ break;
281
+
282
+ case KeyCode.pageDown:
283
+ cursor.setMonth(cursor.getMonth() + 1);
284
+ this.moveCursor(e, cursor, { movePage: true });
285
+ break;
286
+
287
+ case KeyCode.home:
288
+ cursor.setDate(1);
289
+ this.moveCursor(e, cursor, { movePage: true });
290
+ break;
291
+
292
+ case KeyCode.end:
293
+ cursor.setMonth(cursor.getMonth() + 1);
294
+ cursor.setDate(0);
295
+ this.moveCursor(e, cursor, { movePage: true });
296
+ break;
297
+
298
+ default:
299
+ let { instance } = this.props;
300
+ let { widget } = instance;
301
+ if (widget.onKeyDown) instance.invoke("onKeyDown", e, instance);
302
+ break;
303
+ }
304
+ }
305
+
306
+ handleWheel(e) {
307
+ e.preventDefault();
308
+ e.stopPropagation();
309
+
310
+ let cursor = new Date(this.state.cursor);
311
+
312
+ if (e.deltaY < 0) {
313
+ cursor.setMonth(cursor.getMonth() - 1);
314
+ this.moveCursor(e, cursor, { movePage: true });
315
+ } else if (e.deltaY > 0) {
316
+ cursor.setMonth(cursor.getMonth() + 1);
317
+ this.moveCursor(e, cursor, { movePage: true });
318
+ }
319
+ }
320
+
321
+ handleBlur(e) {
322
+ FocusManager.nudge();
323
+ let { instance } = this.props;
324
+ let { widget } = instance;
325
+ if (widget.onBlur) instance.invoke("onBlur", e, instance);
326
+ this.setState({
327
+ focus: false,
328
+ });
329
+ }
330
+
331
+ handleFocus(e) {
332
+ oneFocusOut(this, this.el, this.handleFocusOut.bind(this));
333
+ this.setState({
334
+ focus: true,
335
+ });
336
+ }
337
+
338
+ handleFocusOut() {
339
+ let { instance } = this.props;
340
+ let { widget } = instance;
341
+ if (widget.onFocusOut) instance.invoke("onFocusOut", null, instance);
342
+ }
343
+
344
+ handleMouseLeave(e) {
345
+ tooltipMouseLeave(e, ...getFieldTooltip(this.props.instance));
346
+ this.setState({
347
+ hover: false,
348
+ });
349
+ }
350
+
351
+ handleMouseEnter(e) {
352
+ this.setState({
353
+ hover: true,
354
+ });
355
+ }
356
+
357
+ handleMouseMove(e) {
358
+ this.moveCursor(e, readDate(e.target.dataset));
359
+ }
360
+
361
+ handleMouseDown(e) {
362
+ this.props.handleSelect(e, readDate(e.target.dataset));
363
+ }
364
+
365
+ componentDidMount() {
366
+ //calendar doesn't bring up keyboard so it's ok to focus it even on mobile
367
+ if (this.props.instance.widget.autoFocus) this.el.focus();
368
+
369
+ tooltipParentDidMount(this.el, ...getFieldTooltip(this.props.instance));
370
+ }
371
+
372
+ UNSAFE_componentWillReceiveProps(props) {
373
+ let { data } = props.instance;
374
+ if (data.date)
375
+ this.setState({
376
+ ...this.getPage(data.date),
377
+ value: data.date,
378
+ });
379
+
380
+ tooltipParentWillReceiveProps(this.el, ...getFieldTooltip(props.instance));
381
+ }
382
+
383
+ componentWillUnmount() {
384
+ offFocusOut(this);
385
+ tooltipParentWillUnmount(this.props.instance);
386
+ }
387
+
388
+ render() {
389
+ let { data, widget } = this.props.instance;
390
+ let { CSS, baseClass, disabledDaysOfWeek, startWithMonday } = widget;
391
+
392
+ let { refDate, startDate, endDate } = this.getPage(this.state.refDate);
393
+
394
+ let month = refDate.getMonth();
395
+ let year = refDate.getFullYear();
396
+ let weeks = [];
397
+ let date = startDate;
398
+
399
+ let empty = {};
400
+
401
+ let today = zeroTime(new Date());
402
+ while (date >= startDate && date <= endDate) {
403
+ let days = [];
404
+ for (let i = 0; i < 7; i++) {
405
+ let dayInfo = (data.dayData && data.dayData[date.toDateString()]) || empty;
406
+ let unselectable = !validationCheck(date, data, disabledDaysOfWeek);
407
+ let classNames = CSS.expand(
408
+ CSS.element(baseClass, "day", {
409
+ outside: month != date.getMonth(),
410
+ unselectable: unselectable,
411
+ selected: data.date && sameDate(data.date, date),
412
+ cursor:
413
+ (this.state.hover || this.state.focus) && this.state.cursor && sameDate(this.state.cursor, date),
414
+ today: widget.highlightToday && sameDate(date, today),
415
+ }),
416
+ dayInfo.className,
417
+ CSS.mod(dayInfo.mod)
418
+ );
419
+ let dateInst = new Date(date);
420
+ days.push(
421
+ <td
422
+ key={i}
423
+ className={classNames}
424
+ style={CSS.parseStyle(dayInfo.style)}
425
+ data-year={dateInst.getFullYear()}
426
+ data-month={dateInst.getMonth() + 1}
427
+ data-date={dateInst.getDate()}
428
+ onMouseMove={unselectable ? null : this.handleMouseMove}
429
+ onMouseDown={unselectable ? null : this.handleMouseDown}
430
+ >
431
+ {date.getDate()}
432
+ </td>
433
+ );
434
+ date.setDate(date.getDate() + 1);
435
+ }
436
+ weeks.push(
437
+ <tr key={weeks.length} className={CSS.element(baseClass, "week")}>
438
+ <td />
439
+ {days}
440
+ <td />
441
+ </tr>
442
+ );
443
+ }
444
+
445
+ let culture = Culture.getDateTimeCulture();
446
+ let monthNames = culture.getMonthNames("long");
447
+ let dayNames = culture.getWeekdayNames("short").map((x) => x.substr(0, 2));
448
+ if (startWithMonday) dayNames = [...dayNames.slice(1), dayNames[0]];
449
+
450
+ return (
451
+ <div
452
+ className={data.classNames}
453
+ tabIndex={data.disabled || !data.focusable ? null : data.tabIndex || 0}
454
+ onKeyDown={(e) => this.handleKeyPress(e)}
455
+ onMouseDown={(e) => {
456
+ // prevent losing focus from the input field
457
+ if (!data.focusable) {
458
+ e.preventDefault();
459
+ }
460
+ e.stopPropagation();
461
+ }}
462
+ ref={(el) => {
463
+ this.el = el;
464
+ }}
465
+ onMouseMove={(e) => tooltipMouseMove(e, ...getFieldTooltip(this.props.instance))}
466
+ onMouseLeave={(e) => this.handleMouseLeave(e)}
467
+ onMouseEnter={(e) => this.handleMouseEnter(e)}
468
+ onWheel={(e) => this.handleWheel(e)}
469
+ onFocus={(e) => this.handleFocus(e)}
470
+ onBlur={(e) => this.handleBlur(e)}
471
+ >
472
+ <table>
473
+ <thead>
474
+ <tr key="h" className={CSS.element(baseClass, "header")}>
475
+ <td />
476
+ <td onClick={(e) => this.move(e, "y", -1)}>
477
+ <ForwardIcon className={CSS.element(baseClass, "icon-prev-year")} />
478
+ </td>
479
+ <td onClick={(e) => this.move(e, "m", -1)}>
480
+ <DropdownIcon className={CSS.element(baseClass, "icon-prev-month")} />
481
+ </td>
482
+ <th className={CSS.element(baseClass, "display")} colSpan="3">
483
+ {monthNames[month]}
484
+ <br />
485
+ {year}
486
+ </th>
487
+ <td onClick={(e) => this.move(e, "m", +1)}>
488
+ <DropdownIcon className={CSS.element(baseClass, "icon-next-month")} />
489
+ </td>
490
+ <td onClick={(e) => this.move(e, "y", +1)}>
491
+ <ForwardIcon className={CSS.element(baseClass, "icon-next-year")} />
492
+ </td>
493
+ <td />
494
+ </tr>
495
+ <tr key="d" className={CSS.element(baseClass, "day-names")}>
496
+ <td />
497
+ {dayNames.map((name, i) => (
498
+ <th key={i}>{name}</th>
499
+ ))}
500
+ <td />
501
+ </tr>
502
+ </thead>
503
+ <tbody>{weeks}</tbody>
504
+ </table>
505
+ {widget.showTodayButton && (
506
+ <div className={CSS.element(baseClass, "toolbar")}>
507
+ <button
508
+ className={CSS.expand(CSS.element(baseClass, "today-button"), CSS.block("button", "hollow"))}
509
+ data-year={today.getFullYear()}
510
+ data-month={today.getMonth() + 1}
511
+ data-date={today.getDate()}
512
+ onClick={(e) => {
513
+ this.handleMouseDown(e);
514
+ }}
515
+ >
516
+ {widget.todayButtonText}
517
+ </button>
518
+ </div>
519
+ )}
520
+ </div>
521
+ );
522
+ }
523
+ }
524
+
525
+ const readDate = (ds) => new Date(Number(ds.year), Number(ds.month) - 1, Number(ds.date));
526
+
527
+ Widget.alias("calendar", Calendar);