cx 25.6.2 → 25.6.3

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