cx 25.1.0 → 25.1.1

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