cx 25.2.1 → 25.4.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.
Files changed (116) hide show
  1. package/dist/charts.css +8 -0
  2. package/dist/charts.js +29 -2
  3. package/dist/manifest.js +682 -682
  4. package/dist/widgets.js +7 -2
  5. package/package.json +1 -1
  6. package/src/charts/Legend.d.ts +18 -0
  7. package/src/charts/Legend.js +22 -2
  8. package/src/charts/Legend.scss +8 -0
  9. package/src/charts/LegendEntry.js +128 -128
  10. package/src/charts/LegendEntry.scss +27 -27
  11. package/src/charts/PieChart.d.ts +92 -92
  12. package/src/charts/PieChart.js +1 -0
  13. package/src/charts/axis/Axis.d.ts +113 -113
  14. package/src/charts/axis/Axis.js +280 -280
  15. package/src/charts/axis/CategoryAxis.d.ts +30 -30
  16. package/src/charts/axis/CategoryAxis.js +241 -241
  17. package/src/charts/axis/NumericAxis.js +351 -351
  18. package/src/charts/axis/Stack.js +55 -55
  19. package/src/charts/axis/TimeAxis.d.ts +28 -28
  20. package/src/charts/axis/TimeAxis.js +611 -611
  21. package/src/charts/helpers/PointReducer.js +47 -47
  22. package/src/charts/helpers/SnapPointFinder.js +69 -69
  23. package/src/core.d.ts +40 -1
  24. package/src/data/Binding.spec.js +69 -69
  25. package/src/data/Expression.js +229 -229
  26. package/src/data/Expression.spec.js +229 -229
  27. package/src/data/StringTemplate.js +92 -92
  28. package/src/data/StringTemplate.spec.js +132 -132
  29. package/src/data/StructuredSelector.js +132 -132
  30. package/src/data/View.d.ts +36 -12
  31. package/src/data/getAccessor.spec.js +11 -11
  32. package/src/data/getSelector.js +49 -49
  33. package/src/hooks/createLocalStorageRef.d.ts +3 -3
  34. package/src/hooks/createLocalStorageRef.js +20 -20
  35. package/src/index.scss +6 -6
  36. package/src/ui/Culture.d.ts +57 -57
  37. package/src/ui/Culture.js +139 -139
  38. package/src/ui/FocusManager.js +171 -171
  39. package/src/ui/Format.js +108 -108
  40. package/src/ui/HoverSync.js +147 -147
  41. package/src/ui/Instance.d.ts +72 -72
  42. package/src/ui/Instance.js +614 -614
  43. package/src/ui/Repeater.d.ts +61 -61
  44. package/src/ui/ResizeManager.d.ts +4 -3
  45. package/src/ui/index.d.ts +42 -42
  46. package/src/ui/layout/ContentPlaceholder.d.ts +19 -19
  47. package/src/ui/layout/ContentPlaceholder.js +105 -105
  48. package/src/ui/layout/ContentPlaceholder.spec.js +579 -579
  49. package/src/ui/layout/LabelsTopLayout.js +134 -134
  50. package/src/util/Format.js +270 -270
  51. package/src/util/date/encodeDate.d.ts +1 -1
  52. package/src/util/date/encodeDate.js +8 -8
  53. package/src/util/date/encodeDateWithTimezoneOffset.d.ts +1 -1
  54. package/src/util/date/index.d.ts +11 -11
  55. package/src/util/date/index.js +11 -11
  56. package/src/util/date/parseDateInvariant.d.ts +3 -3
  57. package/src/util/date/parseDateInvariant.js +20 -20
  58. package/src/util/debounce.d.ts +3 -4
  59. package/src/util/getSearchQueryPredicate.js +59 -59
  60. package/src/util/index.d.ts +51 -51
  61. package/src/util/index.js +54 -54
  62. package/src/util/isValidIdentifierName.d.ts +1 -1
  63. package/src/util/isValidIdentifierName.js +5 -5
  64. package/src/util/isValidIdentifierName.spec.js +33 -33
  65. package/src/util/scss/add-rules.scss +38 -38
  66. package/src/widgets/CxCredit.scss +37 -37
  67. package/src/widgets/HighlightedSearchText.js +36 -36
  68. package/src/widgets/HighlightedSearchText.scss +18 -18
  69. package/src/widgets/HtmlElement.d.ts +4 -0
  70. package/src/widgets/List.scss +91 -91
  71. package/src/widgets/drag-drop/DropZone.js +214 -214
  72. package/src/widgets/form/Calendar.js +618 -618
  73. package/src/widgets/form/Calendar.scss +196 -196
  74. package/src/widgets/form/Checkbox.scss +127 -127
  75. package/src/widgets/form/ColorField.js +397 -397
  76. package/src/widgets/form/ColorField.scss +96 -96
  77. package/src/widgets/form/ColorPicker.js +8 -3
  78. package/src/widgets/form/ColorPicker.scss +283 -283
  79. package/src/widgets/form/DateTimeField.js +576 -576
  80. package/src/widgets/form/DateTimePicker.js +392 -392
  81. package/src/widgets/form/LookupField.d.ts +179 -179
  82. package/src/widgets/form/LookupField.scss +219 -219
  83. package/src/widgets/form/MonthField.d.ts +99 -99
  84. package/src/widgets/form/MonthField.js +523 -523
  85. package/src/widgets/form/MonthPicker.d.ts +76 -76
  86. package/src/widgets/form/MonthPicker.js +641 -641
  87. package/src/widgets/form/MonthPicker.scss +118 -118
  88. package/src/widgets/form/NumberField.js +459 -459
  89. package/src/widgets/form/NumberField.scss +61 -61
  90. package/src/widgets/form/Radio.scss +121 -121
  91. package/src/widgets/form/Select.scss +99 -99
  92. package/src/widgets/form/Slider.scss +118 -118
  93. package/src/widgets/form/Switch.scss +140 -140
  94. package/src/widgets/form/TextArea.scss +43 -43
  95. package/src/widgets/form/TextField.js +290 -290
  96. package/src/widgets/form/TextField.scss +55 -55
  97. package/src/widgets/form/UploadButton.d.ts +34 -34
  98. package/src/widgets/form/variables.scss +353 -353
  99. package/src/widgets/grid/Grid.d.ts +442 -442
  100. package/src/widgets/grid/Grid.js +3414 -3414
  101. package/src/widgets/grid/GridRow.js +228 -228
  102. package/src/widgets/grid/TreeNode.d.ts +23 -23
  103. package/src/widgets/grid/TreeNode.scss +88 -88
  104. package/src/widgets/grid/variables.scss +133 -133
  105. package/src/widgets/nav/LinkButton.js +128 -128
  106. package/src/widgets/nav/Menu.scss +74 -74
  107. package/src/widgets/overlay/Dropdown.js +612 -612
  108. package/src/widgets/overlay/FlyweightTooltipTracker.js +39 -39
  109. package/src/widgets/overlay/Overlay.d.ts +73 -73
  110. package/src/widgets/overlay/Window.js +202 -202
  111. package/src/widgets/overlay/captureMouse.js +124 -124
  112. package/src/widgets/overlay/createHotPromiseWindowFactory.d.ts +18 -18
  113. package/src/widgets/overlay/createHotPromiseWindowFactory.js +56 -56
  114. package/src/widgets/overlay/index.d.ts +11 -11
  115. package/src/widgets/overlay/index.js +11 -11
  116. package/src/widgets/variables.scss +144 -144
@@ -1,641 +1,641 @@
1
- import { Widget, VDOM } from "../../ui/Widget";
2
- import { Field, getFieldTooltip } from "./Field";
3
- import { Culture } from "../../ui/Culture";
4
- import { FocusManager, oneFocusOut, offFocusOut, preventFocusOnTouch } from "../../ui/FocusManager";
5
- import { StringTemplate } from "../../data/StringTemplate";
6
- import { monthStart } from "../../util/date/monthStart";
7
- import { dateDiff } from "../../util/date/dateDiff";
8
- import { minDate } from "../../util/date/minDate";
9
- import { maxDate } from "../../util/date/maxDate";
10
- import { lowerBoundCheck } from "../../util/date/lowerBoundCheck";
11
- import { upperBoundCheck } from "../../util/date/upperBoundCheck";
12
- import { Console } from "../../util/Console";
13
- import { KeyCode } from "../../util/KeyCode";
14
- import {
15
- tooltipParentWillReceiveProps,
16
- tooltipParentWillUnmount,
17
- tooltipMouseMove,
18
- tooltipMouseLeave,
19
- tooltipParentDidMount,
20
- } from "../overlay/tooltip-ops";
21
- import { Localization } from "../../ui/Localization";
22
- import { scrollElementIntoView } from "../../util/scrollElementIntoView";
23
- import { stopPropagation } from "../../util/eventCallbacks";
24
- import { isString } from "../../util/isString";
25
- import { isTouchEvent } from "../../util/isTouchEvent";
26
- import { getCursorPos } from "../overlay/captureMouse";
27
-
28
- import { enableCultureSensitiveFormatting } from "../../ui/Format";
29
- import { parseDateInvariant } from "../../util";
30
- enableCultureSensitiveFormatting();
31
-
32
- export class MonthPicker extends Field {
33
- declareData() {
34
- let values = {};
35
-
36
- if (this.mode == "range") {
37
- this.range = true;
38
- this.mode = "edit";
39
- Console.warn('Please use the range flag on MonthPickers. Syntax mode="range" is deprecated.', this);
40
- }
41
-
42
- if (this.range) {
43
- values = {
44
- from: null,
45
- to: null,
46
- };
47
- } else {
48
- values = {
49
- value: this.emptyValue,
50
- };
51
- }
52
-
53
- super.declareData(
54
- values,
55
- {
56
- refDate: undefined,
57
- disabled: undefined,
58
- minValue: undefined,
59
- minExclusive: undefined,
60
- maxValue: undefined,
61
- maxExclusive: undefined,
62
- },
63
- ...arguments,
64
- );
65
- }
66
-
67
- init() {
68
- super.init();
69
- }
70
-
71
- prepareData(context, { data }) {
72
- data.stateMods = {
73
- disabled: data.disabled,
74
- };
75
-
76
- if (!this.range && data.value) data.date = monthStart(parseDateInvariant(data.value));
77
-
78
- if (this.range) {
79
- if (data.from) data.from = monthStart(parseDateInvariant(data.from));
80
-
81
- if (data.to) {
82
- let date = parseDateInvariant(data.to);
83
- if (this.inclusiveTo) date.setDate(date.getDate() + 1);
84
- data.to = monthStart(date);
85
- }
86
- }
87
-
88
- if (data.refDate) data.refDate = monthStart(parseDateInvariant(data.refDate));
89
-
90
- if (data.maxValue) data.maxValue = monthStart(parseDateInvariant(data.maxValue));
91
-
92
- if (data.minValue) data.minValue = monthStart(parseDateInvariant(data.minValue));
93
-
94
- super.prepareData(...arguments);
95
- }
96
-
97
- validate(context, instance) {
98
- super.validate(context, instance);
99
- let { data } = instance;
100
- if (!data.error && data.date) {
101
- let d;
102
- if (data.maxValue) {
103
- d = dateDiff(data.date, data.maxValue);
104
- if (d > 0) data.error = StringTemplate.format(this.maxValueErrorText, data.maxValue);
105
- else if (d == 0 && data.maxExclusive)
106
- data.error = StringTemplate.format(this.maxExclusiveErrorText, data.maxValue);
107
- }
108
-
109
- if (data.minValue) {
110
- d = dateDiff(data.date, data.minValue);
111
- if (d < 0) data.error = StringTemplate.format(this.minValueErrorText, data.minValue);
112
- else if (d == 0 && data.minExclusive)
113
- data.error = StringTemplate.format(this.minExclusiveErrorText, data.minValue);
114
- }
115
- }
116
- }
117
-
118
- renderInput(context, instance, key) {
119
- return (
120
- <MonthPickerComponent
121
- key={key}
122
- instance={instance}
123
- onBlur={this.onBlur}
124
- onFocusOut={this.onFocusOut}
125
- onKeyDown={this.onKeyDown}
126
- autoFocus={this.autoFocus}
127
- />
128
- );
129
- }
130
-
131
- handleSelect(e, instance, date1, date2) {
132
- let { data, widget } = instance;
133
- let encode = widget.encoding || Culture.getDefaultDateEncoding();
134
-
135
- if (data.disabled) return;
136
-
137
- if (!validationCheck(date1, data)) return;
138
-
139
- if (this.onBeforeSelect && instance.invoke("onBeforeSelect", e, instance, date1, date2) === false) return;
140
-
141
- if (this.range) {
142
- instance.set("from", encode(date1));
143
- let toDate = new Date(date2);
144
- if (this.inclusiveTo) toDate.setDate(toDate.getDate() - 1);
145
- instance.set("to", encode(toDate));
146
- } else instance.set("value", encode(date1));
147
-
148
- if (this.onSelect) instance.invoke("onSelect", instance, date1, date2);
149
- }
150
- }
151
-
152
- MonthPicker.prototype.baseClass = "monthpicker";
153
- MonthPicker.prototype.range = false;
154
- MonthPicker.prototype.startYear = 1980;
155
- MonthPicker.prototype.endYear = 2030;
156
- MonthPicker.prototype.bufferSize = 15;
157
-
158
- // Localization
159
- MonthPicker.prototype.maxValueErrorText = "Select {0:d} or before.";
160
- MonthPicker.prototype.maxExclusiveErrorText = "Select a date before {0:d}.";
161
- MonthPicker.prototype.minValueErrorText = "Select {0:d} or later.";
162
- MonthPicker.prototype.minExclusiveErrorText = "Select a date after {0:d}.";
163
- MonthPicker.prototype.inclusiveTo = false;
164
-
165
- Localization.registerPrototype("cx/widgets/MonthPicker", MonthPicker);
166
-
167
- Widget.alias("month-picker", MonthPicker);
168
-
169
- const validationCheck = (date, data) => {
170
- if (data.maxValue && !upperBoundCheck(date, data.maxValue, data.maxExclusive)) return false;
171
-
172
- if (data.minValue && !lowerBoundCheck(date, data.minValue, data.minExclusive)) return false;
173
-
174
- return true;
175
- };
176
-
177
- const monthNumber = (date) => {
178
- return date.getFullYear() * 12 + date.getMonth();
179
- };
180
-
181
- export class MonthPickerComponent extends VDOM.Component {
182
- constructor(props) {
183
- super(props);
184
- let { data, widget } = props.instance;
185
-
186
- let cursor = monthStart(data.refDate ? data.refDate : data.date || data.from || new Date());
187
-
188
- this.dom = {};
189
-
190
- this.state = {
191
- cursorYear: cursor.getFullYear(),
192
- cursorMonth: cursor.getMonth() + 1,
193
- cursorQuarter: cursor.getMonth() / 3,
194
- column: "M",
195
- start: widget.startYear,
196
- end: widget.startYear + widget.bufferSize,
197
- };
198
-
199
- this.handleMouseDown = this.handleMouseDown.bind(this);
200
- this.handleMouseUp = this.handleMouseUp.bind(this);
201
- this.handleMouseEnter = this.handleMouseEnter.bind(this);
202
- this.handleKeyPress = this.handleKeyPress.bind(this);
203
- this.handleTouchMove = this.handleTouchMove.bind(this);
204
- this.handleTouchEnd = this.handleTouchEnd.bind(this);
205
- }
206
-
207
- extractCursorInfo(el) {
208
- if (!el.attributes["data-point"].value) return false;
209
- let parts = el.attributes["data-point"].value.split("-");
210
- if (parts[0] != "Y") return false;
211
- let cursor = {
212
- column: "Y",
213
- cursorYear: Number(parts[1]),
214
- };
215
- if (parts.length == 4) {
216
- cursor.column = parts[2];
217
- if (cursor.column == "M") cursor.cursorMonth = Number(parts[3]);
218
- else cursor.cursorQuarter = Number(parts[3]);
219
- }
220
- return cursor;
221
- }
222
-
223
- moveCursor(e, data, options = {}) {
224
- e.preventDefault();
225
- e.stopPropagation();
226
-
227
- if (data.cursorYear) {
228
- let { startYear, endYear } = this.props.instance.widget;
229
- data.cursorYear = Math.max(startYear, Math.min(endYear, data.cursorYear));
230
- }
231
-
232
- if (Object.keys(data).every((k) => this.state[k] == data[k])) return;
233
-
234
- this.setState(data, () => {
235
- if (options.ensureVisible) {
236
- let index = this.state.cursorYear - this.state.start;
237
- let tbody = this.dom.table.children[index];
238
- if (tbody) scrollElementIntoView(tbody);
239
- }
240
- });
241
- }
242
-
243
- handleKeyPress(e) {
244
- let { widget } = this.props.instance;
245
- let { cursorMonth, cursorYear, cursorQuarter, column } = this.state;
246
-
247
- switch (e.keyCode) {
248
- case KeyCode.enter:
249
- // if (widget.range && e.shiftKey && !this.dragStartDates) {
250
- // this.handleMouseDown(e, {}, false);
251
- // } else {
252
- // this.handleMouseUp(e);
253
- // }
254
- this.handleMouseUp(e);
255
- e.preventDefault();
256
- e.stopPropagation();
257
- break;
258
-
259
- case KeyCode.left:
260
- if (column == "Y") this.moveCursor(e, { cursorQuarter: 3, cursorYear: cursorYear - 1, column: "Q" });
261
- else if (column == "Q") this.moveCursor(e, { cursorMonth: cursorQuarter * 4, column: "M" });
262
- else if (column == "M" && (cursorMonth - 1) % 3 == 0) this.moveCursor(e, { column: "Y" });
263
- else this.moveCursor(e, { cursorMonth: cursorMonth - 1 });
264
- break;
265
-
266
- case KeyCode.right:
267
- if (column == "Y") this.moveCursor(e, { cursorMonth: 1, column: "M" });
268
- else if (column == "Q")
269
- this.moveCursor(e, { column: "Y", cursorYear: cursorQuarter == 3 ? cursorYear + 1 : cursorYear });
270
- else if (column == "M" && (cursorMonth - 1) % 3 == 2)
271
- this.moveCursor(e, { column: "Q", cursorQuarter: Math.floor((cursorMonth - 1) / 3) });
272
- else this.moveCursor(e, { cursorMonth: cursorMonth + 1 });
273
- break;
274
-
275
- case KeyCode.up:
276
- if (column == "Y") this.moveCursor(e, { cursorYear: cursorYear - 1 }, { ensureVisible: true });
277
- else if (column == "Q")
278
- this.moveCursor(
279
- e,
280
- {
281
- cursorQuarter: (cursorQuarter + 3) % 4,
282
- cursorYear: cursorQuarter == 0 ? cursorYear - 1 : cursorYear,
283
- },
284
- { ensureVisible: true },
285
- );
286
- else if (column == "M")
287
- if (cursorMonth > 3) this.moveCursor(e, { cursorMonth: cursorMonth - 3 }, { ensureVisible: true });
288
- else
289
- this.moveCursor(
290
- e,
291
- { cursorMonth: cursorMonth + 9, cursorYear: cursorYear - 1 },
292
- { ensureVisible: true },
293
- );
294
- break;
295
-
296
- case KeyCode.down:
297
- if (column == "Y") this.moveCursor(e, { cursorYear: cursorYear + 1 }, { ensureVisible: true });
298
- else if (column == "Q")
299
- this.moveCursor(
300
- e,
301
- {
302
- cursorQuarter: (cursorQuarter + 1) % 4,
303
- cursorYear: cursorQuarter == 3 ? cursorYear + 1 : cursorYear,
304
- },
305
- { ensureVisible: true },
306
- );
307
- else if (column == "M")
308
- if (cursorMonth < 10) this.moveCursor(e, { cursorMonth: cursorMonth + 3 }, { ensureVisible: true });
309
- else
310
- this.moveCursor(
311
- e,
312
- { cursorMonth: cursorMonth - 9, cursorYear: cursorYear + 1 },
313
- { ensureVisible: true },
314
- );
315
- break;
316
-
317
- case KeyCode.pageUp:
318
- this.moveCursor(e, { cursorYear: this.state.cursorYear - 1 });
319
- break;
320
-
321
- case KeyCode.pageDown:
322
- this.moveCursor(e, { cursorYear: this.state.cursorYear + 1 });
323
- break;
324
-
325
- default:
326
- if (this.props.onKeyDown) this.props.onKeyDown(e, this.props.instance);
327
- break;
328
- }
329
- }
330
-
331
- handleBlur(e) {
332
- FocusManager.nudge();
333
- if (this.props.onBlur) this.props.onBlur();
334
- this.setState({
335
- focused: false,
336
- });
337
- }
338
-
339
- handleFocus(e) {
340
- this.setState({
341
- focused: true,
342
- });
343
- if (this.props.onFocusOut) oneFocusOut(this, this.dom.el, this.handleFocusOut.bind(this));
344
- }
345
-
346
- handleFocusOut() {
347
- if (this.props.onFocusOut) this.props.onFocusOut();
348
- }
349
-
350
- getCursorDates(cursor) {
351
- let { cursorMonth, cursorYear, cursorQuarter, column } = cursor || this.state;
352
- switch (column) {
353
- case "M":
354
- return [new Date(cursorYear, cursorMonth - 1, 1), new Date(cursorYear, cursorMonth, 1)];
355
-
356
- case "Q":
357
- return [new Date(cursorYear, cursorQuarter * 3, 1), new Date(cursorYear, cursorQuarter * 3 + 3, 1)];
358
-
359
- case "Y":
360
- return [new Date(cursorYear, 0, 1), new Date(cursorYear + 1, 0, 1)];
361
- }
362
- }
363
-
364
- handleTouchMove(e) {
365
- let cursor = getCursorPos(e);
366
- let el = document.elementFromPoint(cursor.clientX, cursor.clientY);
367
- if (this.dom.table.contains(el) && isString(el.dataset.point)) {
368
- let cursor = this.extractCursorInfo(el);
369
- this.moveCursor(e, cursor);
370
- }
371
- }
372
-
373
- handleTouchEnd(e) {
374
- if (this.state.state == "drag") this.handleMouseUp(e);
375
- }
376
-
377
- handleMouseEnter(e) {
378
- let cursor = this.extractCursorInfo(e.target);
379
- cursor.hover = !isTouchEvent();
380
- this.moveCursor(e, cursor);
381
- }
382
-
383
- handleMouseDown(e, cursor, drag = true) {
384
- let { instance } = this.props;
385
- let { widget } = instance;
386
-
387
- if (!cursor) {
388
- cursor = this.extractCursorInfo(e.currentTarget);
389
- this.moveCursor(e, cursor);
390
- }
391
-
392
- e.stopPropagation();
393
- preventFocusOnTouch(e);
394
-
395
- this.dragStartDates = this.getCursorDates(cursor);
396
- if (drag) {
397
- this.setState({
398
- state: "drag",
399
- ...cursor,
400
- });
401
- }
402
- }
403
-
404
- handleMouseUp(e) {
405
- let { instance } = this.props;
406
- let { widget, data } = instance;
407
-
408
- e.stopPropagation();
409
- e.preventDefault();
410
-
411
- let [cursorFromDate, cursorToDate] = this.getCursorDates();
412
- let originFromDate = cursorFromDate,
413
- originToDate = cursorToDate;
414
- if (widget.range && e.shiftKey) {
415
- if (data.from) originFromDate = data.from;
416
- if (data.to) originToDate = data.to;
417
- } else if (this.state.state == "drag") {
418
- if (widget.range) {
419
- [originFromDate, originToDate] = this.dragStartDates;
420
- }
421
- this.setState({ state: "normal" });
422
- } else {
423
- //skip mouse events originated somewhere else
424
- if (e.type != "keydown") return;
425
- }
426
- widget.handleSelect(e, instance, minDate(originFromDate, cursorFromDate), maxDate(originToDate, cursorToDate));
427
- }
428
-
429
- render() {
430
- let { data, widget } = this.props.instance;
431
- let { CSS, baseClass, startYear, endYear } = widget;
432
-
433
- let years = [];
434
-
435
- let { start, end } = this.state;
436
-
437
- let from = 10000,
438
- to = 0,
439
- a,
440
- b;
441
-
442
- if (data.date && !widget.range) {
443
- from = monthNumber(data.date);
444
- to = from + 0.1;
445
- } else if (widget.range) {
446
- if (this.state.state == "drag") {
447
- let [originFromDate, originToDate] = this.dragStartDates;
448
- let [cursorFromDate, cursorToDate] = this.getCursorDates();
449
- a = Math.min(monthNumber(originFromDate), monthNumber(cursorFromDate));
450
- b = Math.max(monthNumber(originToDate), monthNumber(cursorToDate));
451
- from = Math.min(a, b);
452
- to = Math.max(a, b);
453
- } else if (data.from && data.to) {
454
- a = monthNumber(data.from);
455
- b = monthNumber(data.to);
456
- from = Math.min(a, b);
457
- to = Math.max(a, b);
458
- }
459
- }
460
-
461
- let monthNames = Culture.getDateTimeCulture().getMonthNames("short");
462
- let showCursor = this.state.hover || this.state.focused;
463
-
464
- for (let y = start; y <= end; y++) {
465
- let rows = [];
466
- for (let q = 0; q < 4; q++) {
467
- let row = [];
468
- if (q == 0)
469
- row.push(
470
- <th
471
- key="year"
472
- rowSpan={4}
473
- data-point={`Y-${y}`}
474
- className={CSS.element(baseClass, "year", {
475
- cursor: showCursor && this.state.column == "Y" && y == this.state.cursorYear,
476
- })}
477
- onMouseEnter={this.handleMouseEnter}
478
- onMouseDown={this.handleMouseDown}
479
- onMouseUp={this.handleMouseUp}
480
- >
481
- {y}
482
- </th>,
483
- );
484
-
485
- for (let i = 0; i < 3; i++) {
486
- let m = q * 3 + i + 1;
487
- let unselectable = !validationCheck(new Date(y, m - 1, 1), data);
488
- let mno = y * 12 + m - 1;
489
- let handle = true; //isTouchDevice(); //mno === from || mno === to - 1;
490
- row.push(
491
- <td
492
- key={`M${m}`}
493
- className={CSS.state({
494
- cursor:
495
- showCursor &&
496
- this.state.column == "M" &&
497
- y == this.state.cursorYear &&
498
- m == this.state.cursorMonth,
499
- handle,
500
- selected: mno >= from && mno < to,
501
- unselectable,
502
- })}
503
- data-point={`Y-${y}-M-${m}`}
504
- onMouseEnter={unselectable ? null : this.handleMouseEnter}
505
- onMouseDown={unselectable ? null : this.handleMouseDown}
506
- onMouseUp={unselectable ? null : this.handleMouseUp}
507
- onTouchStart={unselectable ? null : this.handleMouseDown}
508
- onTouchMove={unselectable ? null : this.handleTouchMove}
509
- onTouchEnd={this.handleMouseUp}
510
- >
511
- {monthNames[m - 1].substr(0, 3)}
512
- </td>,
513
- );
514
- }
515
- row.push(
516
- <th
517
- key={`q${q}`}
518
- className={CSS.state({
519
- cursor:
520
- showCursor &&
521
- this.state.column == "Q" &&
522
- y == this.state.cursorYear &&
523
- q == this.state.cursorQuarter,
524
- })}
525
- data-point={`Y-${y}-Q-${q}`}
526
- onMouseEnter={this.handleMouseEnter}
527
- onMouseDown={this.handleMouseDown}
528
- onMouseUp={this.handleMouseUp}
529
- >
530
- {`Q${q + 1}`}
531
- </th>,
532
- );
533
- rows.push(row);
534
- }
535
- years.push(rows);
536
- }
537
-
538
- return (
539
- <div
540
- ref={(el) => {
541
- this.dom.el = el;
542
- }}
543
- className={data.classNames}
544
- style={data.style}
545
- tabIndex={data.disabled ? null : data.tabIndex || 0}
546
- onKeyDown={this.handleKeyPress}
547
- onMouseDown={stopPropagation}
548
- onMouseMove={(e) => tooltipMouseMove(e, ...getFieldTooltip(this.props.instance))}
549
- onMouseLeave={this.handleMouseLeave.bind(this)}
550
- onFocus={(e) => this.handleFocus(e)}
551
- onBlur={this.handleBlur.bind(this)}
552
- onScroll={this.onScroll.bind(this)}
553
- >
554
- {this.state.yearHeight && <div style={{ height: `${(start - startYear) * this.state.yearHeight}px` }} />}
555
- <table
556
- ref={(el) => {
557
- this.dom.table = el;
558
- }}
559
- >
560
- {years.map((rows, y) => (
561
- <tbody key={start + y}>
562
- {rows.map((cells, i) => (
563
- <tr key={i}>{cells}</tr>
564
- ))}
565
- </tbody>
566
- ))}
567
- </table>
568
- {this.state.yearHeight && (
569
- <div style={{ height: `${Math.max(0, endYear - end) * this.state.yearHeight}px` }} />
570
- )}
571
- </div>
572
- );
573
- }
574
-
575
- onScroll() {
576
- let { startYear, endYear, bufferSize } = this.props.instance.widget;
577
- let visibleItems = ceil5(Math.ceil(this.dom.el.offsetHeight / this.state.yearHeight));
578
- let start = Math.max(
579
- startYear,
580
- startYear + floor5(Math.floor(this.dom.el.scrollTop / this.state.yearHeight)) - visibleItems,
581
- );
582
- if (start != this.state.start && start + bufferSize <= endYear) {
583
- this.setState({
584
- start,
585
- end: start + 15,
586
- });
587
- }
588
- }
589
-
590
- handleMouseLeave(e) {
591
- tooltipMouseLeave(e, ...getFieldTooltip(this.props.instance));
592
- this.moveCursor(e, {
593
- hover: false,
594
- });
595
- }
596
-
597
- componentDidMount() {
598
- //non-input, ok to focus on mobile
599
- if (this.props.autoFocus) this.dom.el.focus();
600
-
601
- tooltipParentDidMount(this.dom.el, ...getFieldTooltip(this.props.instance));
602
- let yearHeight = this.dom.table.scrollHeight / (this.props.instance.widget.bufferSize + 1);
603
- this.setState(
604
- {
605
- yearHeight: yearHeight,
606
- },
607
- () => {
608
- let { widget, data } = this.props.instance;
609
- let { startYear } = widget;
610
- let yearCount = 1;
611
- if (widget.range && data.from && data.to) {
612
- yearCount = data.to.getFullYear() - data.from.getFullYear() + 1;
613
- if (data.to.getMonth() == 0 && data.to.getDate() == 1) yearCount--;
614
- }
615
- this.dom.el.scrollTop =
616
- (this.state.cursorYear - startYear + yearCount / 2) * this.state.yearHeight -
617
- this.dom.el.offsetHeight / 2;
618
- },
619
- );
620
- }
621
-
622
- UNSAFE_componentWillReceiveProps(props) {
623
- this.setState({
624
- state: "normal",
625
- });
626
- tooltipParentWillReceiveProps(this.dom.el, ...getFieldTooltip(props.instance));
627
- }
628
-
629
- componentWillUnmount() {
630
- offFocusOut(this);
631
- tooltipParentWillUnmount(this.props.instance);
632
- }
633
- }
634
-
635
- function ceil5(x) {
636
- return Math.ceil(x / 5) * 5;
637
- }
638
-
639
- function floor5(x) {
640
- return Math.floor(x / 5) * 5;
641
- }
1
+ import { Widget, VDOM } from "../../ui/Widget";
2
+ import { Field, getFieldTooltip } from "./Field";
3
+ import { Culture } from "../../ui/Culture";
4
+ import { FocusManager, oneFocusOut, offFocusOut, preventFocusOnTouch } from "../../ui/FocusManager";
5
+ import { StringTemplate } from "../../data/StringTemplate";
6
+ import { monthStart } from "../../util/date/monthStart";
7
+ import { dateDiff } from "../../util/date/dateDiff";
8
+ import { minDate } from "../../util/date/minDate";
9
+ import { maxDate } from "../../util/date/maxDate";
10
+ import { lowerBoundCheck } from "../../util/date/lowerBoundCheck";
11
+ import { upperBoundCheck } from "../../util/date/upperBoundCheck";
12
+ import { Console } from "../../util/Console";
13
+ import { KeyCode } from "../../util/KeyCode";
14
+ import {
15
+ tooltipParentWillReceiveProps,
16
+ tooltipParentWillUnmount,
17
+ tooltipMouseMove,
18
+ tooltipMouseLeave,
19
+ tooltipParentDidMount,
20
+ } from "../overlay/tooltip-ops";
21
+ import { Localization } from "../../ui/Localization";
22
+ import { scrollElementIntoView } from "../../util/scrollElementIntoView";
23
+ import { stopPropagation } from "../../util/eventCallbacks";
24
+ import { isString } from "../../util/isString";
25
+ import { isTouchEvent } from "../../util/isTouchEvent";
26
+ import { getCursorPos } from "../overlay/captureMouse";
27
+
28
+ import { enableCultureSensitiveFormatting } from "../../ui/Format";
29
+ import { parseDateInvariant } from "../../util";
30
+ enableCultureSensitiveFormatting();
31
+
32
+ export class MonthPicker extends Field {
33
+ declareData() {
34
+ let values = {};
35
+
36
+ if (this.mode == "range") {
37
+ this.range = true;
38
+ this.mode = "edit";
39
+ Console.warn('Please use the range flag on MonthPickers. Syntax mode="range" is deprecated.', this);
40
+ }
41
+
42
+ if (this.range) {
43
+ values = {
44
+ from: null,
45
+ to: null,
46
+ };
47
+ } else {
48
+ values = {
49
+ value: this.emptyValue,
50
+ };
51
+ }
52
+
53
+ super.declareData(
54
+ values,
55
+ {
56
+ refDate: undefined,
57
+ disabled: undefined,
58
+ minValue: undefined,
59
+ minExclusive: undefined,
60
+ maxValue: undefined,
61
+ maxExclusive: undefined,
62
+ },
63
+ ...arguments,
64
+ );
65
+ }
66
+
67
+ init() {
68
+ super.init();
69
+ }
70
+
71
+ prepareData(context, { data }) {
72
+ data.stateMods = {
73
+ disabled: data.disabled,
74
+ };
75
+
76
+ if (!this.range && data.value) data.date = monthStart(parseDateInvariant(data.value));
77
+
78
+ if (this.range) {
79
+ if (data.from) data.from = monthStart(parseDateInvariant(data.from));
80
+
81
+ if (data.to) {
82
+ let date = parseDateInvariant(data.to);
83
+ if (this.inclusiveTo) date.setDate(date.getDate() + 1);
84
+ data.to = monthStart(date);
85
+ }
86
+ }
87
+
88
+ if (data.refDate) data.refDate = monthStart(parseDateInvariant(data.refDate));
89
+
90
+ if (data.maxValue) data.maxValue = monthStart(parseDateInvariant(data.maxValue));
91
+
92
+ if (data.minValue) data.minValue = monthStart(parseDateInvariant(data.minValue));
93
+
94
+ super.prepareData(...arguments);
95
+ }
96
+
97
+ validate(context, instance) {
98
+ super.validate(context, instance);
99
+ let { data } = instance;
100
+ if (!data.error && data.date) {
101
+ let d;
102
+ if (data.maxValue) {
103
+ d = dateDiff(data.date, data.maxValue);
104
+ if (d > 0) data.error = StringTemplate.format(this.maxValueErrorText, data.maxValue);
105
+ else if (d == 0 && data.maxExclusive)
106
+ data.error = StringTemplate.format(this.maxExclusiveErrorText, data.maxValue);
107
+ }
108
+
109
+ if (data.minValue) {
110
+ d = dateDiff(data.date, data.minValue);
111
+ if (d < 0) data.error = StringTemplate.format(this.minValueErrorText, data.minValue);
112
+ else if (d == 0 && data.minExclusive)
113
+ data.error = StringTemplate.format(this.minExclusiveErrorText, data.minValue);
114
+ }
115
+ }
116
+ }
117
+
118
+ renderInput(context, instance, key) {
119
+ return (
120
+ <MonthPickerComponent
121
+ key={key}
122
+ instance={instance}
123
+ onBlur={this.onBlur}
124
+ onFocusOut={this.onFocusOut}
125
+ onKeyDown={this.onKeyDown}
126
+ autoFocus={this.autoFocus}
127
+ />
128
+ );
129
+ }
130
+
131
+ handleSelect(e, instance, date1, date2) {
132
+ let { data, widget } = instance;
133
+ let encode = widget.encoding || Culture.getDefaultDateEncoding();
134
+
135
+ if (data.disabled) return;
136
+
137
+ if (!validationCheck(date1, data)) return;
138
+
139
+ if (this.onBeforeSelect && instance.invoke("onBeforeSelect", e, instance, date1, date2) === false) return;
140
+
141
+ if (this.range) {
142
+ instance.set("from", encode(date1));
143
+ let toDate = new Date(date2);
144
+ if (this.inclusiveTo) toDate.setDate(toDate.getDate() - 1);
145
+ instance.set("to", encode(toDate));
146
+ } else instance.set("value", encode(date1));
147
+
148
+ if (this.onSelect) instance.invoke("onSelect", instance, date1, date2);
149
+ }
150
+ }
151
+
152
+ MonthPicker.prototype.baseClass = "monthpicker";
153
+ MonthPicker.prototype.range = false;
154
+ MonthPicker.prototype.startYear = 1980;
155
+ MonthPicker.prototype.endYear = 2030;
156
+ MonthPicker.prototype.bufferSize = 15;
157
+
158
+ // Localization
159
+ MonthPicker.prototype.maxValueErrorText = "Select {0:d} or before.";
160
+ MonthPicker.prototype.maxExclusiveErrorText = "Select a date before {0:d}.";
161
+ MonthPicker.prototype.minValueErrorText = "Select {0:d} or later.";
162
+ MonthPicker.prototype.minExclusiveErrorText = "Select a date after {0:d}.";
163
+ MonthPicker.prototype.inclusiveTo = false;
164
+
165
+ Localization.registerPrototype("cx/widgets/MonthPicker", MonthPicker);
166
+
167
+ Widget.alias("month-picker", MonthPicker);
168
+
169
+ const validationCheck = (date, data) => {
170
+ if (data.maxValue && !upperBoundCheck(date, data.maxValue, data.maxExclusive)) return false;
171
+
172
+ if (data.minValue && !lowerBoundCheck(date, data.minValue, data.minExclusive)) return false;
173
+
174
+ return true;
175
+ };
176
+
177
+ const monthNumber = (date) => {
178
+ return date.getFullYear() * 12 + date.getMonth();
179
+ };
180
+
181
+ export class MonthPickerComponent extends VDOM.Component {
182
+ constructor(props) {
183
+ super(props);
184
+ let { data, widget } = props.instance;
185
+
186
+ let cursor = monthStart(data.refDate ? data.refDate : data.date || data.from || new Date());
187
+
188
+ this.dom = {};
189
+
190
+ this.state = {
191
+ cursorYear: cursor.getFullYear(),
192
+ cursorMonth: cursor.getMonth() + 1,
193
+ cursorQuarter: cursor.getMonth() / 3,
194
+ column: "M",
195
+ start: widget.startYear,
196
+ end: widget.startYear + widget.bufferSize,
197
+ };
198
+
199
+ this.handleMouseDown = this.handleMouseDown.bind(this);
200
+ this.handleMouseUp = this.handleMouseUp.bind(this);
201
+ this.handleMouseEnter = this.handleMouseEnter.bind(this);
202
+ this.handleKeyPress = this.handleKeyPress.bind(this);
203
+ this.handleTouchMove = this.handleTouchMove.bind(this);
204
+ this.handleTouchEnd = this.handleTouchEnd.bind(this);
205
+ }
206
+
207
+ extractCursorInfo(el) {
208
+ if (!el.attributes["data-point"].value) return false;
209
+ let parts = el.attributes["data-point"].value.split("-");
210
+ if (parts[0] != "Y") return false;
211
+ let cursor = {
212
+ column: "Y",
213
+ cursorYear: Number(parts[1]),
214
+ };
215
+ if (parts.length == 4) {
216
+ cursor.column = parts[2];
217
+ if (cursor.column == "M") cursor.cursorMonth = Number(parts[3]);
218
+ else cursor.cursorQuarter = Number(parts[3]);
219
+ }
220
+ return cursor;
221
+ }
222
+
223
+ moveCursor(e, data, options = {}) {
224
+ e.preventDefault();
225
+ e.stopPropagation();
226
+
227
+ if (data.cursorYear) {
228
+ let { startYear, endYear } = this.props.instance.widget;
229
+ data.cursorYear = Math.max(startYear, Math.min(endYear, data.cursorYear));
230
+ }
231
+
232
+ if (Object.keys(data).every((k) => this.state[k] == data[k])) return;
233
+
234
+ this.setState(data, () => {
235
+ if (options.ensureVisible) {
236
+ let index = this.state.cursorYear - this.state.start;
237
+ let tbody = this.dom.table.children[index];
238
+ if (tbody) scrollElementIntoView(tbody);
239
+ }
240
+ });
241
+ }
242
+
243
+ handleKeyPress(e) {
244
+ let { widget } = this.props.instance;
245
+ let { cursorMonth, cursorYear, cursorQuarter, column } = this.state;
246
+
247
+ switch (e.keyCode) {
248
+ case KeyCode.enter:
249
+ // if (widget.range && e.shiftKey && !this.dragStartDates) {
250
+ // this.handleMouseDown(e, {}, false);
251
+ // } else {
252
+ // this.handleMouseUp(e);
253
+ // }
254
+ this.handleMouseUp(e);
255
+ e.preventDefault();
256
+ e.stopPropagation();
257
+ break;
258
+
259
+ case KeyCode.left:
260
+ if (column == "Y") this.moveCursor(e, { cursorQuarter: 3, cursorYear: cursorYear - 1, column: "Q" });
261
+ else if (column == "Q") this.moveCursor(e, { cursorMonth: cursorQuarter * 4, column: "M" });
262
+ else if (column == "M" && (cursorMonth - 1) % 3 == 0) this.moveCursor(e, { column: "Y" });
263
+ else this.moveCursor(e, { cursorMonth: cursorMonth - 1 });
264
+ break;
265
+
266
+ case KeyCode.right:
267
+ if (column == "Y") this.moveCursor(e, { cursorMonth: 1, column: "M" });
268
+ else if (column == "Q")
269
+ this.moveCursor(e, { column: "Y", cursorYear: cursorQuarter == 3 ? cursorYear + 1 : cursorYear });
270
+ else if (column == "M" && (cursorMonth - 1) % 3 == 2)
271
+ this.moveCursor(e, { column: "Q", cursorQuarter: Math.floor((cursorMonth - 1) / 3) });
272
+ else this.moveCursor(e, { cursorMonth: cursorMonth + 1 });
273
+ break;
274
+
275
+ case KeyCode.up:
276
+ if (column == "Y") this.moveCursor(e, { cursorYear: cursorYear - 1 }, { ensureVisible: true });
277
+ else if (column == "Q")
278
+ this.moveCursor(
279
+ e,
280
+ {
281
+ cursorQuarter: (cursorQuarter + 3) % 4,
282
+ cursorYear: cursorQuarter == 0 ? cursorYear - 1 : cursorYear,
283
+ },
284
+ { ensureVisible: true },
285
+ );
286
+ else if (column == "M")
287
+ if (cursorMonth > 3) this.moveCursor(e, { cursorMonth: cursorMonth - 3 }, { ensureVisible: true });
288
+ else
289
+ this.moveCursor(
290
+ e,
291
+ { cursorMonth: cursorMonth + 9, cursorYear: cursorYear - 1 },
292
+ { ensureVisible: true },
293
+ );
294
+ break;
295
+
296
+ case KeyCode.down:
297
+ if (column == "Y") this.moveCursor(e, { cursorYear: cursorYear + 1 }, { ensureVisible: true });
298
+ else if (column == "Q")
299
+ this.moveCursor(
300
+ e,
301
+ {
302
+ cursorQuarter: (cursorQuarter + 1) % 4,
303
+ cursorYear: cursorQuarter == 3 ? cursorYear + 1 : cursorYear,
304
+ },
305
+ { ensureVisible: true },
306
+ );
307
+ else if (column == "M")
308
+ if (cursorMonth < 10) this.moveCursor(e, { cursorMonth: cursorMonth + 3 }, { ensureVisible: true });
309
+ else
310
+ this.moveCursor(
311
+ e,
312
+ { cursorMonth: cursorMonth - 9, cursorYear: cursorYear + 1 },
313
+ { ensureVisible: true },
314
+ );
315
+ break;
316
+
317
+ case KeyCode.pageUp:
318
+ this.moveCursor(e, { cursorYear: this.state.cursorYear - 1 });
319
+ break;
320
+
321
+ case KeyCode.pageDown:
322
+ this.moveCursor(e, { cursorYear: this.state.cursorYear + 1 });
323
+ break;
324
+
325
+ default:
326
+ if (this.props.onKeyDown) this.props.onKeyDown(e, this.props.instance);
327
+ break;
328
+ }
329
+ }
330
+
331
+ handleBlur(e) {
332
+ FocusManager.nudge();
333
+ if (this.props.onBlur) this.props.onBlur();
334
+ this.setState({
335
+ focused: false,
336
+ });
337
+ }
338
+
339
+ handleFocus(e) {
340
+ this.setState({
341
+ focused: true,
342
+ });
343
+ if (this.props.onFocusOut) oneFocusOut(this, this.dom.el, this.handleFocusOut.bind(this));
344
+ }
345
+
346
+ handleFocusOut() {
347
+ if (this.props.onFocusOut) this.props.onFocusOut();
348
+ }
349
+
350
+ getCursorDates(cursor) {
351
+ let { cursorMonth, cursorYear, cursorQuarter, column } = cursor || this.state;
352
+ switch (column) {
353
+ case "M":
354
+ return [new Date(cursorYear, cursorMonth - 1, 1), new Date(cursorYear, cursorMonth, 1)];
355
+
356
+ case "Q":
357
+ return [new Date(cursorYear, cursorQuarter * 3, 1), new Date(cursorYear, cursorQuarter * 3 + 3, 1)];
358
+
359
+ case "Y":
360
+ return [new Date(cursorYear, 0, 1), new Date(cursorYear + 1, 0, 1)];
361
+ }
362
+ }
363
+
364
+ handleTouchMove(e) {
365
+ let cursor = getCursorPos(e);
366
+ let el = document.elementFromPoint(cursor.clientX, cursor.clientY);
367
+ if (this.dom.table.contains(el) && isString(el.dataset.point)) {
368
+ let cursor = this.extractCursorInfo(el);
369
+ this.moveCursor(e, cursor);
370
+ }
371
+ }
372
+
373
+ handleTouchEnd(e) {
374
+ if (this.state.state == "drag") this.handleMouseUp(e);
375
+ }
376
+
377
+ handleMouseEnter(e) {
378
+ let cursor = this.extractCursorInfo(e.target);
379
+ cursor.hover = !isTouchEvent();
380
+ this.moveCursor(e, cursor);
381
+ }
382
+
383
+ handleMouseDown(e, cursor, drag = true) {
384
+ let { instance } = this.props;
385
+ let { widget } = instance;
386
+
387
+ if (!cursor) {
388
+ cursor = this.extractCursorInfo(e.currentTarget);
389
+ this.moveCursor(e, cursor);
390
+ }
391
+
392
+ e.stopPropagation();
393
+ preventFocusOnTouch(e);
394
+
395
+ this.dragStartDates = this.getCursorDates(cursor);
396
+ if (drag) {
397
+ this.setState({
398
+ state: "drag",
399
+ ...cursor,
400
+ });
401
+ }
402
+ }
403
+
404
+ handleMouseUp(e) {
405
+ let { instance } = this.props;
406
+ let { widget, data } = instance;
407
+
408
+ e.stopPropagation();
409
+ e.preventDefault();
410
+
411
+ let [cursorFromDate, cursorToDate] = this.getCursorDates();
412
+ let originFromDate = cursorFromDate,
413
+ originToDate = cursorToDate;
414
+ if (widget.range && e.shiftKey) {
415
+ if (data.from) originFromDate = data.from;
416
+ if (data.to) originToDate = data.to;
417
+ } else if (this.state.state == "drag") {
418
+ if (widget.range) {
419
+ [originFromDate, originToDate] = this.dragStartDates;
420
+ }
421
+ this.setState({ state: "normal" });
422
+ } else {
423
+ //skip mouse events originated somewhere else
424
+ if (e.type != "keydown") return;
425
+ }
426
+ widget.handleSelect(e, instance, minDate(originFromDate, cursorFromDate), maxDate(originToDate, cursorToDate));
427
+ }
428
+
429
+ render() {
430
+ let { data, widget } = this.props.instance;
431
+ let { CSS, baseClass, startYear, endYear } = widget;
432
+
433
+ let years = [];
434
+
435
+ let { start, end } = this.state;
436
+
437
+ let from = 10000,
438
+ to = 0,
439
+ a,
440
+ b;
441
+
442
+ if (data.date && !widget.range) {
443
+ from = monthNumber(data.date);
444
+ to = from + 0.1;
445
+ } else if (widget.range) {
446
+ if (this.state.state == "drag") {
447
+ let [originFromDate, originToDate] = this.dragStartDates;
448
+ let [cursorFromDate, cursorToDate] = this.getCursorDates();
449
+ a = Math.min(monthNumber(originFromDate), monthNumber(cursorFromDate));
450
+ b = Math.max(monthNumber(originToDate), monthNumber(cursorToDate));
451
+ from = Math.min(a, b);
452
+ to = Math.max(a, b);
453
+ } else if (data.from && data.to) {
454
+ a = monthNumber(data.from);
455
+ b = monthNumber(data.to);
456
+ from = Math.min(a, b);
457
+ to = Math.max(a, b);
458
+ }
459
+ }
460
+
461
+ let monthNames = Culture.getDateTimeCulture().getMonthNames("short");
462
+ let showCursor = this.state.hover || this.state.focused;
463
+
464
+ for (let y = start; y <= end; y++) {
465
+ let rows = [];
466
+ for (let q = 0; q < 4; q++) {
467
+ let row = [];
468
+ if (q == 0)
469
+ row.push(
470
+ <th
471
+ key="year"
472
+ rowSpan={4}
473
+ data-point={`Y-${y}`}
474
+ className={CSS.element(baseClass, "year", {
475
+ cursor: showCursor && this.state.column == "Y" && y == this.state.cursorYear,
476
+ })}
477
+ onMouseEnter={this.handleMouseEnter}
478
+ onMouseDown={this.handleMouseDown}
479
+ onMouseUp={this.handleMouseUp}
480
+ >
481
+ {y}
482
+ </th>,
483
+ );
484
+
485
+ for (let i = 0; i < 3; i++) {
486
+ let m = q * 3 + i + 1;
487
+ let unselectable = !validationCheck(new Date(y, m - 1, 1), data);
488
+ let mno = y * 12 + m - 1;
489
+ let handle = true; //isTouchDevice(); //mno === from || mno === to - 1;
490
+ row.push(
491
+ <td
492
+ key={`M${m}`}
493
+ className={CSS.state({
494
+ cursor:
495
+ showCursor &&
496
+ this.state.column == "M" &&
497
+ y == this.state.cursorYear &&
498
+ m == this.state.cursorMonth,
499
+ handle,
500
+ selected: mno >= from && mno < to,
501
+ unselectable,
502
+ })}
503
+ data-point={`Y-${y}-M-${m}`}
504
+ onMouseEnter={unselectable ? null : this.handleMouseEnter}
505
+ onMouseDown={unselectable ? null : this.handleMouseDown}
506
+ onMouseUp={unselectable ? null : this.handleMouseUp}
507
+ onTouchStart={unselectable ? null : this.handleMouseDown}
508
+ onTouchMove={unselectable ? null : this.handleTouchMove}
509
+ onTouchEnd={this.handleMouseUp}
510
+ >
511
+ {monthNames[m - 1].substr(0, 3)}
512
+ </td>,
513
+ );
514
+ }
515
+ row.push(
516
+ <th
517
+ key={`q${q}`}
518
+ className={CSS.state({
519
+ cursor:
520
+ showCursor &&
521
+ this.state.column == "Q" &&
522
+ y == this.state.cursorYear &&
523
+ q == this.state.cursorQuarter,
524
+ })}
525
+ data-point={`Y-${y}-Q-${q}`}
526
+ onMouseEnter={this.handleMouseEnter}
527
+ onMouseDown={this.handleMouseDown}
528
+ onMouseUp={this.handleMouseUp}
529
+ >
530
+ {`Q${q + 1}`}
531
+ </th>,
532
+ );
533
+ rows.push(row);
534
+ }
535
+ years.push(rows);
536
+ }
537
+
538
+ return (
539
+ <div
540
+ ref={(el) => {
541
+ this.dom.el = el;
542
+ }}
543
+ className={data.classNames}
544
+ style={data.style}
545
+ tabIndex={data.disabled ? null : data.tabIndex || 0}
546
+ onKeyDown={this.handleKeyPress}
547
+ onMouseDown={stopPropagation}
548
+ onMouseMove={(e) => tooltipMouseMove(e, ...getFieldTooltip(this.props.instance))}
549
+ onMouseLeave={this.handleMouseLeave.bind(this)}
550
+ onFocus={(e) => this.handleFocus(e)}
551
+ onBlur={this.handleBlur.bind(this)}
552
+ onScroll={this.onScroll.bind(this)}
553
+ >
554
+ {this.state.yearHeight && <div style={{ height: `${(start - startYear) * this.state.yearHeight}px` }} />}
555
+ <table
556
+ ref={(el) => {
557
+ this.dom.table = el;
558
+ }}
559
+ >
560
+ {years.map((rows, y) => (
561
+ <tbody key={start + y}>
562
+ {rows.map((cells, i) => (
563
+ <tr key={i}>{cells}</tr>
564
+ ))}
565
+ </tbody>
566
+ ))}
567
+ </table>
568
+ {this.state.yearHeight && (
569
+ <div style={{ height: `${Math.max(0, endYear - end) * this.state.yearHeight}px` }} />
570
+ )}
571
+ </div>
572
+ );
573
+ }
574
+
575
+ onScroll() {
576
+ let { startYear, endYear, bufferSize } = this.props.instance.widget;
577
+ let visibleItems = ceil5(Math.ceil(this.dom.el.offsetHeight / this.state.yearHeight));
578
+ let start = Math.max(
579
+ startYear,
580
+ startYear + floor5(Math.floor(this.dom.el.scrollTop / this.state.yearHeight)) - visibleItems,
581
+ );
582
+ if (start != this.state.start && start + bufferSize <= endYear) {
583
+ this.setState({
584
+ start,
585
+ end: start + 15,
586
+ });
587
+ }
588
+ }
589
+
590
+ handleMouseLeave(e) {
591
+ tooltipMouseLeave(e, ...getFieldTooltip(this.props.instance));
592
+ this.moveCursor(e, {
593
+ hover: false,
594
+ });
595
+ }
596
+
597
+ componentDidMount() {
598
+ //non-input, ok to focus on mobile
599
+ if (this.props.autoFocus) this.dom.el.focus();
600
+
601
+ tooltipParentDidMount(this.dom.el, ...getFieldTooltip(this.props.instance));
602
+ let yearHeight = this.dom.table.scrollHeight / (this.props.instance.widget.bufferSize + 1);
603
+ this.setState(
604
+ {
605
+ yearHeight: yearHeight,
606
+ },
607
+ () => {
608
+ let { widget, data } = this.props.instance;
609
+ let { startYear } = widget;
610
+ let yearCount = 1;
611
+ if (widget.range && data.from && data.to) {
612
+ yearCount = data.to.getFullYear() - data.from.getFullYear() + 1;
613
+ if (data.to.getMonth() == 0 && data.to.getDate() == 1) yearCount--;
614
+ }
615
+ this.dom.el.scrollTop =
616
+ (this.state.cursorYear - startYear + yearCount / 2) * this.state.yearHeight -
617
+ this.dom.el.offsetHeight / 2;
618
+ },
619
+ );
620
+ }
621
+
622
+ UNSAFE_componentWillReceiveProps(props) {
623
+ this.setState({
624
+ state: "normal",
625
+ });
626
+ tooltipParentWillReceiveProps(this.dom.el, ...getFieldTooltip(props.instance));
627
+ }
628
+
629
+ componentWillUnmount() {
630
+ offFocusOut(this);
631
+ tooltipParentWillUnmount(this.props.instance);
632
+ }
633
+ }
634
+
635
+ function ceil5(x) {
636
+ return Math.ceil(x / 5) * 5;
637
+ }
638
+
639
+ function floor5(x) {
640
+ return Math.floor(x / 5) * 5;
641
+ }