cx 25.4.0 → 25.5.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 (138) hide show
  1. package/dist/charts.js +2 -2
  2. package/dist/data.js +17 -3
  3. package/dist/manifest.js +651 -651
  4. package/dist/ui.js +88 -61
  5. package/dist/widgets.js +13 -13
  6. package/package.json +1 -1
  7. package/src/charts/Legend.d.ts +6 -6
  8. package/src/charts/LegendEntry.js +128 -128
  9. package/src/charts/LegendEntry.scss +27 -27
  10. package/src/charts/PieChart.d.ts +92 -92
  11. package/src/charts/RangeMarker.js +6 -2
  12. package/src/charts/axis/Axis.d.ts +113 -113
  13. package/src/charts/axis/Axis.js +280 -280
  14. package/src/charts/axis/CategoryAxis.d.ts +30 -30
  15. package/src/charts/axis/CategoryAxis.js +241 -241
  16. package/src/charts/axis/NumericAxis.d.ts +46 -46
  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 +31 -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/data/AugmentedViewBase.js +77 -75
  24. package/src/data/Binding.spec.js +69 -69
  25. package/src/data/ExposedRecordView.js +75 -70
  26. package/src/data/ExposedValueView.js +73 -72
  27. package/src/data/Expression.js +229 -229
  28. package/src/data/Expression.spec.js +229 -229
  29. package/src/data/Ref.d.ts +24 -24
  30. package/src/data/Ref.spec.js +79 -79
  31. package/src/data/StoreRef.spec.js +24 -24
  32. package/src/data/StringTemplate.js +92 -92
  33. package/src/data/StringTemplate.spec.js +132 -132
  34. package/src/data/StructuredDataAccessor.d.ts +7 -7
  35. package/src/data/StructuredSelector.js +132 -132
  36. package/src/data/SubscribableView.js +54 -54
  37. package/src/data/getAccessor.spec.js +11 -11
  38. package/src/data/getSelector.js +49 -49
  39. package/src/hooks/createLocalStorageRef.d.ts +3 -3
  40. package/src/hooks/createLocalStorageRef.js +20 -20
  41. package/src/index.scss +6 -6
  42. package/src/ui/Container.js +154 -183
  43. package/src/ui/Culture.d.ts +57 -57
  44. package/src/ui/Culture.js +139 -139
  45. package/src/ui/Cx.js +3 -3
  46. package/src/ui/DataProxy.js +45 -44
  47. package/src/ui/DetachedScope.js +98 -94
  48. package/src/ui/FocusManager.js +171 -171
  49. package/src/ui/Format.js +108 -108
  50. package/src/ui/HoverSync.js +147 -147
  51. package/src/ui/Instance.d.ts +1 -1
  52. package/src/ui/Instance.js +25 -16
  53. package/src/ui/IsolatedScope.js +30 -30
  54. package/src/ui/Repeater.d.ts +61 -61
  55. package/src/ui/Rescope.js +35 -31
  56. package/src/ui/Restate.js +167 -163
  57. package/src/ui/Widget.js +184 -200
  58. package/src/ui/adapter/ArrayAdapter.js +152 -142
  59. package/src/ui/adapter/TreeAdapter.js +101 -100
  60. package/src/ui/createFunctionalComponent.d.ts +1 -1
  61. package/src/ui/createFunctionalComponent.js +31 -36
  62. package/src/ui/layout/ContentPlaceholder.d.ts +19 -19
  63. package/src/ui/layout/ContentPlaceholder.js +105 -105
  64. package/src/ui/layout/ContentPlaceholder.spec.js +579 -579
  65. package/src/ui/layout/LabelsTopLayout.js +134 -134
  66. package/src/ui/layout/exploreChildren.d.ts +12 -15
  67. package/src/ui/layout/exploreChildren.js +27 -40
  68. package/src/util/Format.js +270 -270
  69. package/src/util/date/encodeDate.d.ts +1 -1
  70. package/src/util/date/encodeDate.js +8 -8
  71. package/src/util/date/encodeDateWithTimezoneOffset.d.ts +1 -1
  72. package/src/util/date/index.d.ts +11 -11
  73. package/src/util/date/index.js +11 -11
  74. package/src/util/date/parseDateInvariant.d.ts +3 -3
  75. package/src/util/date/parseDateInvariant.js +20 -20
  76. package/src/util/debounce.js +18 -18
  77. package/src/util/getSearchQueryPredicate.js +59 -59
  78. package/src/util/index.d.ts +51 -51
  79. package/src/util/index.js +54 -54
  80. package/src/util/isValidIdentifierName.d.ts +1 -1
  81. package/src/util/isValidIdentifierName.js +5 -5
  82. package/src/util/isValidIdentifierName.spec.js +33 -33
  83. package/src/util/scss/add-rules.scss +38 -38
  84. package/src/util/validatedDebounce.js +19 -19
  85. package/src/widgets/Button.js +118 -118
  86. package/src/widgets/CxCredit.scss +37 -37
  87. package/src/widgets/HighlightedSearchText.js +36 -36
  88. package/src/widgets/HighlightedSearchText.scss +18 -18
  89. package/src/widgets/List.scss +91 -91
  90. package/src/widgets/Sandbox.js +9 -8
  91. package/src/widgets/drag-drop/DropZone.js +214 -214
  92. package/src/widgets/form/Calendar.d.ts +86 -86
  93. package/src/widgets/form/Calendar.js +618 -618
  94. package/src/widgets/form/Calendar.scss +196 -196
  95. package/src/widgets/form/Checkbox.scss +127 -127
  96. package/src/widgets/form/ColorField.js +397 -397
  97. package/src/widgets/form/ColorField.scss +96 -96
  98. package/src/widgets/form/ColorPicker.scss +283 -283
  99. package/src/widgets/form/DateTimeField.js +576 -576
  100. package/src/widgets/form/DateTimePicker.js +392 -392
  101. package/src/widgets/form/LookupField.d.ts +179 -179
  102. package/src/widgets/form/LookupField.scss +219 -219
  103. package/src/widgets/form/MonthField.d.ts +99 -99
  104. package/src/widgets/form/MonthField.js +523 -523
  105. package/src/widgets/form/MonthPicker.d.ts +76 -76
  106. package/src/widgets/form/MonthPicker.js +641 -641
  107. package/src/widgets/form/MonthPicker.scss +118 -118
  108. package/src/widgets/form/NumberField.js +459 -459
  109. package/src/widgets/form/NumberField.scss +61 -61
  110. package/src/widgets/form/Radio.scss +121 -121
  111. package/src/widgets/form/Select.scss +99 -99
  112. package/src/widgets/form/Slider.scss +118 -118
  113. package/src/widgets/form/Switch.scss +140 -140
  114. package/src/widgets/form/TextArea.scss +43 -43
  115. package/src/widgets/form/TextField.js +290 -290
  116. package/src/widgets/form/TextField.scss +55 -55
  117. package/src/widgets/form/UploadButton.d.ts +34 -34
  118. package/src/widgets/form/variables.scss +353 -353
  119. package/src/widgets/grid/Grid.d.ts +442 -442
  120. package/src/widgets/grid/Grid.js +3414 -3414
  121. package/src/widgets/grid/GridRow.js +228 -228
  122. package/src/widgets/grid/TreeNode.d.ts +23 -23
  123. package/src/widgets/grid/TreeNode.scss +88 -88
  124. package/src/widgets/grid/variables.scss +133 -133
  125. package/src/widgets/nav/LinkButton.js +128 -128
  126. package/src/widgets/nav/Menu.scss +74 -74
  127. package/src/widgets/nav/Route.js +102 -106
  128. package/src/widgets/overlay/Dropdown.js +612 -612
  129. package/src/widgets/overlay/FlyweightTooltipTracker.js +39 -39
  130. package/src/widgets/overlay/Overlay.d.ts +73 -73
  131. package/src/widgets/overlay/Tooltip.js +1 -1
  132. package/src/widgets/overlay/Window.js +202 -202
  133. package/src/widgets/overlay/captureMouse.js +124 -124
  134. package/src/widgets/overlay/createHotPromiseWindowFactory.d.ts +18 -18
  135. package/src/widgets/overlay/createHotPromiseWindowFactory.js +56 -56
  136. package/src/widgets/overlay/index.d.ts +11 -11
  137. package/src/widgets/overlay/index.js +11 -11
  138. package/src/widgets/variables.scss +144 -144
@@ -1,523 +1,523 @@
1
- import { DateTimeCulture } from "intl-io";
2
- import { StringTemplate } from "../../data/StringTemplate";
3
- import { Culture } from "../../ui";
4
- import { Cx } from "../../ui/Cx";
5
- import { Localization } from "../../ui/Localization";
6
- import { VDOM, Widget, getContent } from "../../ui/Widget";
7
- import { Console } from "../../util/Console";
8
- import { Format } from "../../util/Format";
9
- import { KeyCode } from "../../util/KeyCode";
10
- import { dateDiff } from "../../util/date/dateDiff";
11
- import { parseDateInvariant } from "../../util";
12
- import { monthStart } from "../../util/date/monthStart";
13
- import { stopPropagation } from "../../util/eventCallbacks";
14
- import { isDefined } from "../../util/isDefined";
15
- import { isTouchDevice } from "../../util/isTouchDevice";
16
- import { isTouchEvent } from "../../util/isTouchEvent";
17
- import { autoFocus } from "../autoFocus";
18
- import ClearIcon from "../icons/clear";
19
- import DropdownIcon from "../icons/drop-down";
20
- import { Dropdown } from "../overlay/Dropdown";
21
- import {
22
- tooltipMouseLeave,
23
- tooltipMouseMove,
24
- tooltipParentDidMount,
25
- tooltipParentWillReceiveProps,
26
- tooltipParentWillUnmount,
27
- } from "../overlay/tooltip-ops";
28
- import { Field, getFieldTooltip } from "./Field";
29
- import { MonthPicker } from "./MonthPicker";
30
- import { getActiveElement } from "../../util/getActiveElement";
31
-
32
- export class MonthField extends Field {
33
- declareData() {
34
- if (this.mode == "range") {
35
- this.range = true;
36
- this.mode = "edit";
37
- Console.warn('Please use the range flag on MonthFields. Syntax mode="range" is deprecated.', this);
38
- }
39
-
40
- let values = {};
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
- disabled: undefined,
57
- readOnly: undefined,
58
- enabled: undefined,
59
- placeholder: undefined,
60
- required: undefined,
61
- minValue: undefined,
62
- minExclusive: undefined,
63
- maxValue: undefined,
64
- maxExclusive: undefined,
65
- icon: undefined,
66
- },
67
- ...arguments,
68
- );
69
- }
70
-
71
- isEmpty(data) {
72
- return this.range ? data.from == null : data.value == null;
73
- }
74
-
75
- init() {
76
- if (!this.culture) this.culture = new DateTimeCulture(Format.culture);
77
-
78
- if (isDefined(this.hideClear)) this.showClear = !this.hideClear;
79
-
80
- if (this.alwaysShowClear) this.showClear = true;
81
-
82
- super.init();
83
- }
84
-
85
- prepareData(context, instance) {
86
- super.prepareData(context, instance);
87
-
88
- let { data } = instance;
89
-
90
- let formatOptions = {
91
- year: "numeric",
92
- month: "short",
93
- };
94
-
95
- if (!this.range && data.value) {
96
- data.date = parseDateInvariant(data.value);
97
- data.formatted = this.culture.format(data.date, formatOptions);
98
- } else if (this.range && data.from && data.to) {
99
- data.from = parseDateInvariant(data.from);
100
- data.to = parseDateInvariant(data.to);
101
- if (!this.inclusiveTo) data.to.setDate(data.to.getDate() - 1);
102
- let fromStr = this.culture.format(data.from, formatOptions);
103
- let toStr = this.culture.format(data.to, formatOptions);
104
- if (fromStr != toStr) data.formatted = fromStr + " - " + toStr;
105
- else data.formatted = fromStr;
106
- }
107
-
108
- if (data.refDate) data.refDate = monthStart(parseDateInvariant(data.refDate));
109
-
110
- if (data.maxValue) data.maxValue = monthStart(parseDateInvariant(data.maxValue));
111
-
112
- if (data.minValue) data.minValue = monthStart(parseDateInvariant(data.minValue));
113
-
114
- instance.lastDropdown = context.lastDropdown;
115
- }
116
-
117
- validateRequired(context, instance) {
118
- var { data } = instance;
119
- if (this.range) {
120
- if (!data.from || !data.to) return this.requiredText;
121
- } else return super.validateRequired(context, instance);
122
- }
123
-
124
- validate(context, instance) {
125
- super.validate(context, instance);
126
- var { data } = instance;
127
- if (!data.error && data.date) {
128
- var d;
129
- if (data.maxValue) {
130
- d = dateDiff(data.date, data.maxValue);
131
- if (d > 0) data.error = StringTemplate.format(this.maxValueErrorText, data.maxValue);
132
- else if (d == 0 && data.maxExclusive)
133
- data.error = StringTemplate.format(this.maxExclusiveErrorText, data.maxValue);
134
- }
135
-
136
- if (data.minValue) {
137
- d = dateDiff(data.date, data.minValue);
138
- if (d < 0) data.error = StringTemplate.format(this.minValueErrorText, data.minValue);
139
- else if (d == 0 && data.minExclusive)
140
- data.error = StringTemplate.format(this.minExclusiveErrorText, data.minValue);
141
- }
142
- }
143
- }
144
-
145
- renderInput(context, instance, key) {
146
- return (
147
- <MonthInput
148
- key={key}
149
- data={instance.data}
150
- instance={instance}
151
- monthPicker={{
152
- value: this.value,
153
- from: this.from,
154
- to: this.to,
155
- range: this.range,
156
- minValue: this.minValue,
157
- maxValue: this.maxValue,
158
- minExclusive: this.minExclusive,
159
- maxExclusive: this.maxExclusive,
160
- maxValueErrorText: this.maxValueErrorText,
161
- maxExclusiveErrorText: this.maxExclusiveErrorText,
162
- minValueErrorText: this.minValueErrorText,
163
- minExclusiveErrorText: this.minExclusiveErrorText,
164
- }}
165
- label={this.labelPlacement && getContent(this.renderLabel(context, instance, "label"))}
166
- help={this.helpPlacement && getContent(this.renderHelp(context, instance, "help"))}
167
- icon={this.renderIcon(context, instance, "icon")}
168
- />
169
- );
170
- }
171
-
172
- formatValue(context, { data }) {
173
- return data.formatted || "";
174
- }
175
-
176
- parseDate(date) {
177
- if (!date) return null;
178
- if (date instanceof Date) return date;
179
- date = this.culture.parse(date, { useCurrentDateForDefaults: true });
180
- return date;
181
- }
182
-
183
- handleSelect(instance, date1, date2) {
184
- let { widget } = instance;
185
- let encode = widget.encoding || Culture.getDefaultDateEncoding();
186
- instance.setState({
187
- inputError: false,
188
- });
189
- if (this.range) {
190
- let d1 = date1 ? encode(date1) : this.emptyValue;
191
- let toDate = date2;
192
- if (date2 && this.inclusiveTo) {
193
- toDate = new Date(date2);
194
- toDate.setDate(toDate.getDate() - 1);
195
- }
196
- let d2 = toDate ? encode(toDate) : this.emptyValue;
197
- instance.set("from", d1);
198
- instance.set("to", d2);
199
- } else {
200
- let value = date1 ? encode(date1) : this.emptyValue;
201
- instance.set("value", value);
202
- }
203
- }
204
- }
205
-
206
- MonthField.prototype.baseClass = "monthfield";
207
- MonthField.prototype.maxValueErrorText = "Select {0:d} or before.";
208
- MonthField.prototype.maxExclusiveErrorText = "Select a date before {0:d}.";
209
- MonthField.prototype.minValueErrorText = "Select {0:d} or later.";
210
- MonthField.prototype.minExclusiveErrorText = "Select a date after {0:d}.";
211
- MonthField.prototype.inputErrorText = "Invalid date entered";
212
- MonthField.prototype.suppressErrorsUntilVisited = true;
213
- MonthField.prototype.icon = "calendar";
214
- MonthField.prototype.showClear = true;
215
- MonthField.prototype.alwaysShowClear = false;
216
- MonthField.prototype.range = false;
217
- MonthField.prototype.reactOn = "enter blur";
218
- MonthField.prototype.inclusiveTo = false;
219
-
220
- Localization.registerPrototype("cx/widgets/MonthField", MonthField);
221
-
222
- Widget.alias("monthfield", MonthField);
223
-
224
- class MonthInput extends VDOM.Component {
225
- constructor(props) {
226
- super(props);
227
- this.props.instance.component = this;
228
- this.state = {
229
- dropdownOpen: false,
230
- focus: false,
231
- };
232
- }
233
-
234
- getDropdown() {
235
- if (this.dropdown) return this.dropdown;
236
-
237
- let { widget, lastDropdown } = this.props.instance;
238
-
239
- var dropdown = {
240
- scrollTracking: true,
241
- inline: !isTouchDevice() || !!lastDropdown,
242
- placementOrder:
243
- "down down-left down-right up up-left up-right right right-up right-down left left-up left-down",
244
- touchFriendly: true,
245
- ...widget.dropdownOptions,
246
- type: Dropdown,
247
- relatedElement: this.input,
248
- items: {
249
- type: MonthPicker,
250
- ...this.props.monthPicker,
251
- encoding: widget.encoding,
252
- inclusiveTo: widget.inclusiveTo,
253
- autoFocus: true,
254
- onFocusOut: (e) => {
255
- this.closeDropdown(e);
256
- },
257
- onKeyDown: (e) => this.onKeyDown(e),
258
- onSelect: (e) => {
259
- let touch = isTouchEvent(e);
260
- this.closeDropdown(e, () => {
261
- if (!touch) this.input.focus();
262
- });
263
- },
264
- },
265
- constrain: true,
266
- firstChildDefinesWidth: true,
267
- };
268
-
269
- return (this.dropdown = Widget.create(dropdown));
270
- }
271
-
272
- render() {
273
- var { instance, label, help, data, icon: iconVDOM } = this.props;
274
- var { widget, state } = instance;
275
- var { CSS, baseClass, suppressErrorsUntilVisited } = widget;
276
-
277
- let insideButton, icon;
278
-
279
- if (!data.readOnly && !data.disabled) {
280
- if (
281
- widget.showClear &&
282
- (((widget.alwaysShowClear || !data.required) && !data.empty) || instance.state.inputError)
283
- )
284
- insideButton = (
285
- <div
286
- className={CSS.element(baseClass, "clear")}
287
- onMouseDown={(e) => {
288
- e.preventDefault();
289
- e.stopPropagation();
290
- }}
291
- onClick={(e) => {
292
- this.onClearClick(e);
293
- }}
294
- >
295
- <ClearIcon className={CSS.element(baseClass, "icon")} />
296
- </div>
297
- );
298
- else
299
- insideButton = (
300
- <div className={CSS.element(baseClass, "right-icon")}>
301
- <DropdownIcon className={CSS.element(baseClass, "icon")} />
302
- </div>
303
- );
304
- }
305
-
306
- if (iconVDOM) {
307
- icon = <div className={CSS.element(baseClass, "left-icon")}>{iconVDOM}</div>;
308
- }
309
-
310
- var dropdown = false;
311
- if (this.state.dropdownOpen)
312
- dropdown = (
313
- <Cx
314
- widget={this.getDropdown()}
315
- parentInstance={instance}
316
- options={{ name: "monthfield-dropdown" }}
317
- subscribe
318
- />
319
- );
320
-
321
- let empty = this.input ? !this.input.value : data.empty;
322
-
323
- return (
324
- <div
325
- className={CSS.expand(
326
- data.classNames,
327
- CSS.state({
328
- visited: state.visited,
329
- focus: this.state.focus || this.state.dropdownOpen,
330
- icon: !!icon,
331
- empty: empty && !data.placeholder,
332
- error: data.error && (state.visited || !suppressErrorsUntilVisited || !empty),
333
- }),
334
- )}
335
- style={data.style}
336
- onMouseDown={this.onMouseDown.bind(this)}
337
- onTouchStart={stopPropagation}
338
- onClick={stopPropagation}
339
- >
340
- <input
341
- id={data.id}
342
- ref={(el) => {
343
- this.input = el;
344
- }}
345
- type="text"
346
- className={CSS.expand(CSS.element(baseClass, "input"), data.inputClass)}
347
- style={data.inputStyle}
348
- defaultValue={data.formatted}
349
- disabled={data.disabled}
350
- readOnly={data.readOnly}
351
- tabIndex={data.tabIndex}
352
- placeholder={data.placeholder}
353
- onInput={(e) => this.onChange(e.target.value, "input")}
354
- onChange={(e) => this.onChange(e.target.value, "change")}
355
- onKeyDown={(e) => this.onKeyDown(e)}
356
- onBlur={(e) => {
357
- this.onBlur(e);
358
- }}
359
- onFocus={(e) => {
360
- this.onFocus(e);
361
- }}
362
- onMouseMove={(e) => tooltipMouseMove(e, ...getFieldTooltip(this.props.instance))}
363
- onMouseLeave={(e) => tooltipMouseLeave(e, ...getFieldTooltip(this.props.instance))}
364
- />
365
- {icon}
366
- {insideButton}
367
- {dropdown}
368
- {label}
369
- {help}
370
- </div>
371
- );
372
- }
373
-
374
- onMouseDown(e) {
375
- e.stopPropagation();
376
-
377
- if (this.state.dropdownOpen) this.closeDropdown(e);
378
- else {
379
- this.openDropdownOnFocus = true;
380
- }
381
-
382
- //icon click
383
- if (e.target != this.input) {
384
- e.preventDefault();
385
- if (!this.state.dropdownOpen) this.openDropdown(e);
386
- else this.input.focus();
387
- }
388
- }
389
-
390
- onFocus(e) {
391
- let { instance } = this.props;
392
- let { widget } = instance;
393
- if (widget.trackFocus) {
394
- this.setState({
395
- focus: true,
396
- });
397
- }
398
- if (this.openDropdownOnFocus) this.openDropdown(e);
399
- }
400
-
401
- onKeyDown(e) {
402
- let { instance } = this.props;
403
- if (instance.widget.handleKeyDown(e, instance) === false) return;
404
-
405
- switch (e.keyCode) {
406
- case KeyCode.enter:
407
- e.stopPropagation();
408
- this.onChange(e.target.value, "enter");
409
- break;
410
-
411
- case KeyCode.esc:
412
- if (this.state.dropdownOpen) {
413
- e.stopPropagation();
414
- this.closeDropdown(e, () => {
415
- this.input.focus();
416
- });
417
- }
418
- break;
419
-
420
- case KeyCode.left:
421
- case KeyCode.right:
422
- e.stopPropagation();
423
- break;
424
-
425
- case KeyCode.down:
426
- this.openDropdown(e);
427
- e.stopPropagation();
428
- e.preventDefault();
429
- break;
430
- }
431
- }
432
-
433
- onBlur(e) {
434
- if (!this.state.dropdownOpen) this.props.instance.setState({ visited: true });
435
-
436
- if (this.state.focus)
437
- this.setState({
438
- focus: false,
439
- });
440
- this.onChange(e.target.value, "blur");
441
- }
442
-
443
- closeDropdown(e, callback) {
444
- if (this.state.dropdownOpen) {
445
- if (this.scrollableParents)
446
- this.scrollableParents.forEach((el) => {
447
- el.removeEventListener("scroll", this.updateDropdownPosition);
448
- });
449
-
450
- this.props.instance.setState({ visited: true });
451
- this.setState({ dropdownOpen: false }, callback);
452
- } else if (callback) callback();
453
- }
454
-
455
- openDropdown(e) {
456
- var { data } = this.props.instance;
457
- this.openDropdownOnFocus = false;
458
-
459
- if (!this.state.dropdownOpen && !(data.disabled || data.readOnly)) {
460
- this.setState({ dropdownOpen: true });
461
- }
462
- }
463
-
464
- onClearClick(e) {
465
- e.stopPropagation();
466
- e.preventDefault();
467
-
468
- var { instance } = this.props;
469
- var { widget } = instance;
470
-
471
- widget.handleSelect(instance, null, null);
472
- }
473
-
474
- UNSAFE_componentWillReceiveProps(props) {
475
- var { data, state } = props.instance;
476
- if (data.formatted != this.input.value && (data.formatted != this.props.data.formatted || !state.inputError)) {
477
- this.input.value = data.formatted || "";
478
- props.instance.setState({
479
- inputError: false,
480
- });
481
- }
482
- tooltipParentWillReceiveProps(this.input, ...getFieldTooltip(this.props.instance));
483
- }
484
-
485
- componentDidMount() {
486
- tooltipParentDidMount(this.input, ...getFieldTooltip(this.props.instance));
487
- autoFocus(this.input, this);
488
- }
489
-
490
- componentDidUpdate() {
491
- autoFocus(this.input, this);
492
- }
493
-
494
- componentWillUnmount() {
495
- if (this.input == getActiveElement() && this.input.value != this.props.data.formatted) {
496
- this.onChange(this.input.value, "blur");
497
- }
498
- tooltipParentWillUnmount(this.props.instance);
499
- }
500
-
501
- onChange(inputValue, eventType) {
502
- var { instance } = this.props;
503
- var { widget } = instance;
504
-
505
- if (widget.reactOn.indexOf(eventType) == -1) return;
506
-
507
- var parts = inputValue.split("-");
508
- var date1 = widget.parseDate(parts[0]);
509
- var date2 = widget.parseDate(parts[1]) || date1;
510
-
511
- if ((date1 != null && isNaN(date1)) || (date2 != null && isNaN(date2))) {
512
- instance.setState({
513
- inputError: widget.inputErrorText,
514
- });
515
- } else if (eventType == "blur" || eventType == "enter") {
516
- if (date2) date2 = new Date(date2.getFullYear(), date2.getMonth() + 1, 1);
517
- instance.setState({
518
- visited: true,
519
- });
520
- widget.handleSelect(instance, date1, date2);
521
- }
522
- }
523
- }
1
+ import { DateTimeCulture } from "intl-io";
2
+ import { StringTemplate } from "../../data/StringTemplate";
3
+ import { Culture } from "../../ui";
4
+ import { Cx } from "../../ui/Cx";
5
+ import { Localization } from "../../ui/Localization";
6
+ import { VDOM, Widget, getContent } from "../../ui/Widget";
7
+ import { Console } from "../../util/Console";
8
+ import { Format } from "../../util/Format";
9
+ import { KeyCode } from "../../util/KeyCode";
10
+ import { dateDiff } from "../../util/date/dateDiff";
11
+ import { parseDateInvariant } from "../../util";
12
+ import { monthStart } from "../../util/date/monthStart";
13
+ import { stopPropagation } from "../../util/eventCallbacks";
14
+ import { isDefined } from "../../util/isDefined";
15
+ import { isTouchDevice } from "../../util/isTouchDevice";
16
+ import { isTouchEvent } from "../../util/isTouchEvent";
17
+ import { autoFocus } from "../autoFocus";
18
+ import ClearIcon from "../icons/clear";
19
+ import DropdownIcon from "../icons/drop-down";
20
+ import { Dropdown } from "../overlay/Dropdown";
21
+ import {
22
+ tooltipMouseLeave,
23
+ tooltipMouseMove,
24
+ tooltipParentDidMount,
25
+ tooltipParentWillReceiveProps,
26
+ tooltipParentWillUnmount,
27
+ } from "../overlay/tooltip-ops";
28
+ import { Field, getFieldTooltip } from "./Field";
29
+ import { MonthPicker } from "./MonthPicker";
30
+ import { getActiveElement } from "../../util/getActiveElement";
31
+
32
+ export class MonthField extends Field {
33
+ declareData() {
34
+ if (this.mode == "range") {
35
+ this.range = true;
36
+ this.mode = "edit";
37
+ Console.warn('Please use the range flag on MonthFields. Syntax mode="range" is deprecated.', this);
38
+ }
39
+
40
+ let values = {};
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
+ disabled: undefined,
57
+ readOnly: undefined,
58
+ enabled: undefined,
59
+ placeholder: undefined,
60
+ required: undefined,
61
+ minValue: undefined,
62
+ minExclusive: undefined,
63
+ maxValue: undefined,
64
+ maxExclusive: undefined,
65
+ icon: undefined,
66
+ },
67
+ ...arguments,
68
+ );
69
+ }
70
+
71
+ isEmpty(data) {
72
+ return this.range ? data.from == null : data.value == null;
73
+ }
74
+
75
+ init() {
76
+ if (!this.culture) this.culture = new DateTimeCulture(Format.culture);
77
+
78
+ if (isDefined(this.hideClear)) this.showClear = !this.hideClear;
79
+
80
+ if (this.alwaysShowClear) this.showClear = true;
81
+
82
+ super.init();
83
+ }
84
+
85
+ prepareData(context, instance) {
86
+ super.prepareData(context, instance);
87
+
88
+ let { data } = instance;
89
+
90
+ let formatOptions = {
91
+ year: "numeric",
92
+ month: "short",
93
+ };
94
+
95
+ if (!this.range && data.value) {
96
+ data.date = parseDateInvariant(data.value);
97
+ data.formatted = this.culture.format(data.date, formatOptions);
98
+ } else if (this.range && data.from && data.to) {
99
+ data.from = parseDateInvariant(data.from);
100
+ data.to = parseDateInvariant(data.to);
101
+ if (!this.inclusiveTo) data.to.setDate(data.to.getDate() - 1);
102
+ let fromStr = this.culture.format(data.from, formatOptions);
103
+ let toStr = this.culture.format(data.to, formatOptions);
104
+ if (fromStr != toStr) data.formatted = fromStr + " - " + toStr;
105
+ else data.formatted = fromStr;
106
+ }
107
+
108
+ if (data.refDate) data.refDate = monthStart(parseDateInvariant(data.refDate));
109
+
110
+ if (data.maxValue) data.maxValue = monthStart(parseDateInvariant(data.maxValue));
111
+
112
+ if (data.minValue) data.minValue = monthStart(parseDateInvariant(data.minValue));
113
+
114
+ instance.lastDropdown = context.lastDropdown;
115
+ }
116
+
117
+ validateRequired(context, instance) {
118
+ var { data } = instance;
119
+ if (this.range) {
120
+ if (!data.from || !data.to) return this.requiredText;
121
+ } else return super.validateRequired(context, instance);
122
+ }
123
+
124
+ validate(context, instance) {
125
+ super.validate(context, instance);
126
+ var { data } = instance;
127
+ if (!data.error && data.date) {
128
+ var d;
129
+ if (data.maxValue) {
130
+ d = dateDiff(data.date, data.maxValue);
131
+ if (d > 0) data.error = StringTemplate.format(this.maxValueErrorText, data.maxValue);
132
+ else if (d == 0 && data.maxExclusive)
133
+ data.error = StringTemplate.format(this.maxExclusiveErrorText, data.maxValue);
134
+ }
135
+
136
+ if (data.minValue) {
137
+ d = dateDiff(data.date, data.minValue);
138
+ if (d < 0) data.error = StringTemplate.format(this.minValueErrorText, data.minValue);
139
+ else if (d == 0 && data.minExclusive)
140
+ data.error = StringTemplate.format(this.minExclusiveErrorText, data.minValue);
141
+ }
142
+ }
143
+ }
144
+
145
+ renderInput(context, instance, key) {
146
+ return (
147
+ <MonthInput
148
+ key={key}
149
+ data={instance.data}
150
+ instance={instance}
151
+ monthPicker={{
152
+ value: this.value,
153
+ from: this.from,
154
+ to: this.to,
155
+ range: this.range,
156
+ minValue: this.minValue,
157
+ maxValue: this.maxValue,
158
+ minExclusive: this.minExclusive,
159
+ maxExclusive: this.maxExclusive,
160
+ maxValueErrorText: this.maxValueErrorText,
161
+ maxExclusiveErrorText: this.maxExclusiveErrorText,
162
+ minValueErrorText: this.minValueErrorText,
163
+ minExclusiveErrorText: this.minExclusiveErrorText,
164
+ }}
165
+ label={this.labelPlacement && getContent(this.renderLabel(context, instance, "label"))}
166
+ help={this.helpPlacement && getContent(this.renderHelp(context, instance, "help"))}
167
+ icon={this.renderIcon(context, instance, "icon")}
168
+ />
169
+ );
170
+ }
171
+
172
+ formatValue(context, { data }) {
173
+ return data.formatted || "";
174
+ }
175
+
176
+ parseDate(date) {
177
+ if (!date) return null;
178
+ if (date instanceof Date) return date;
179
+ date = this.culture.parse(date, { useCurrentDateForDefaults: true });
180
+ return date;
181
+ }
182
+
183
+ handleSelect(instance, date1, date2) {
184
+ let { widget } = instance;
185
+ let encode = widget.encoding || Culture.getDefaultDateEncoding();
186
+ instance.setState({
187
+ inputError: false,
188
+ });
189
+ if (this.range) {
190
+ let d1 = date1 ? encode(date1) : this.emptyValue;
191
+ let toDate = date2;
192
+ if (date2 && this.inclusiveTo) {
193
+ toDate = new Date(date2);
194
+ toDate.setDate(toDate.getDate() - 1);
195
+ }
196
+ let d2 = toDate ? encode(toDate) : this.emptyValue;
197
+ instance.set("from", d1);
198
+ instance.set("to", d2);
199
+ } else {
200
+ let value = date1 ? encode(date1) : this.emptyValue;
201
+ instance.set("value", value);
202
+ }
203
+ }
204
+ }
205
+
206
+ MonthField.prototype.baseClass = "monthfield";
207
+ MonthField.prototype.maxValueErrorText = "Select {0:d} or before.";
208
+ MonthField.prototype.maxExclusiveErrorText = "Select a date before {0:d}.";
209
+ MonthField.prototype.minValueErrorText = "Select {0:d} or later.";
210
+ MonthField.prototype.minExclusiveErrorText = "Select a date after {0:d}.";
211
+ MonthField.prototype.inputErrorText = "Invalid date entered";
212
+ MonthField.prototype.suppressErrorsUntilVisited = true;
213
+ MonthField.prototype.icon = "calendar";
214
+ MonthField.prototype.showClear = true;
215
+ MonthField.prototype.alwaysShowClear = false;
216
+ MonthField.prototype.range = false;
217
+ MonthField.prototype.reactOn = "enter blur";
218
+ MonthField.prototype.inclusiveTo = false;
219
+
220
+ Localization.registerPrototype("cx/widgets/MonthField", MonthField);
221
+
222
+ Widget.alias("monthfield", MonthField);
223
+
224
+ class MonthInput extends VDOM.Component {
225
+ constructor(props) {
226
+ super(props);
227
+ this.props.instance.component = this;
228
+ this.state = {
229
+ dropdownOpen: false,
230
+ focus: false,
231
+ };
232
+ }
233
+
234
+ getDropdown() {
235
+ if (this.dropdown) return this.dropdown;
236
+
237
+ let { widget, lastDropdown } = this.props.instance;
238
+
239
+ var dropdown = {
240
+ scrollTracking: true,
241
+ inline: !isTouchDevice() || !!lastDropdown,
242
+ placementOrder:
243
+ "down down-left down-right up up-left up-right right right-up right-down left left-up left-down",
244
+ touchFriendly: true,
245
+ ...widget.dropdownOptions,
246
+ type: Dropdown,
247
+ relatedElement: this.input,
248
+ items: {
249
+ type: MonthPicker,
250
+ ...this.props.monthPicker,
251
+ encoding: widget.encoding,
252
+ inclusiveTo: widget.inclusiveTo,
253
+ autoFocus: true,
254
+ onFocusOut: (e) => {
255
+ this.closeDropdown(e);
256
+ },
257
+ onKeyDown: (e) => this.onKeyDown(e),
258
+ onSelect: (e) => {
259
+ let touch = isTouchEvent(e);
260
+ this.closeDropdown(e, () => {
261
+ if (!touch) this.input.focus();
262
+ });
263
+ },
264
+ },
265
+ constrain: true,
266
+ firstChildDefinesWidth: true,
267
+ };
268
+
269
+ return (this.dropdown = Widget.create(dropdown));
270
+ }
271
+
272
+ render() {
273
+ var { instance, label, help, data, icon: iconVDOM } = this.props;
274
+ var { widget, state } = instance;
275
+ var { CSS, baseClass, suppressErrorsUntilVisited } = widget;
276
+
277
+ let insideButton, icon;
278
+
279
+ if (!data.readOnly && !data.disabled) {
280
+ if (
281
+ widget.showClear &&
282
+ (((widget.alwaysShowClear || !data.required) && !data.empty) || instance.state.inputError)
283
+ )
284
+ insideButton = (
285
+ <div
286
+ className={CSS.element(baseClass, "clear")}
287
+ onMouseDown={(e) => {
288
+ e.preventDefault();
289
+ e.stopPropagation();
290
+ }}
291
+ onClick={(e) => {
292
+ this.onClearClick(e);
293
+ }}
294
+ >
295
+ <ClearIcon className={CSS.element(baseClass, "icon")} />
296
+ </div>
297
+ );
298
+ else
299
+ insideButton = (
300
+ <div className={CSS.element(baseClass, "right-icon")}>
301
+ <DropdownIcon className={CSS.element(baseClass, "icon")} />
302
+ </div>
303
+ );
304
+ }
305
+
306
+ if (iconVDOM) {
307
+ icon = <div className={CSS.element(baseClass, "left-icon")}>{iconVDOM}</div>;
308
+ }
309
+
310
+ var dropdown = false;
311
+ if (this.state.dropdownOpen)
312
+ dropdown = (
313
+ <Cx
314
+ widget={this.getDropdown()}
315
+ parentInstance={instance}
316
+ options={{ name: "monthfield-dropdown" }}
317
+ subscribe
318
+ />
319
+ );
320
+
321
+ let empty = this.input ? !this.input.value : data.empty;
322
+
323
+ return (
324
+ <div
325
+ className={CSS.expand(
326
+ data.classNames,
327
+ CSS.state({
328
+ visited: state.visited,
329
+ focus: this.state.focus || this.state.dropdownOpen,
330
+ icon: !!icon,
331
+ empty: empty && !data.placeholder,
332
+ error: data.error && (state.visited || !suppressErrorsUntilVisited || !empty),
333
+ }),
334
+ )}
335
+ style={data.style}
336
+ onMouseDown={this.onMouseDown.bind(this)}
337
+ onTouchStart={stopPropagation}
338
+ onClick={stopPropagation}
339
+ >
340
+ <input
341
+ id={data.id}
342
+ ref={(el) => {
343
+ this.input = el;
344
+ }}
345
+ type="text"
346
+ className={CSS.expand(CSS.element(baseClass, "input"), data.inputClass)}
347
+ style={data.inputStyle}
348
+ defaultValue={data.formatted}
349
+ disabled={data.disabled}
350
+ readOnly={data.readOnly}
351
+ tabIndex={data.tabIndex}
352
+ placeholder={data.placeholder}
353
+ onInput={(e) => this.onChange(e.target.value, "input")}
354
+ onChange={(e) => this.onChange(e.target.value, "change")}
355
+ onKeyDown={(e) => this.onKeyDown(e)}
356
+ onBlur={(e) => {
357
+ this.onBlur(e);
358
+ }}
359
+ onFocus={(e) => {
360
+ this.onFocus(e);
361
+ }}
362
+ onMouseMove={(e) => tooltipMouseMove(e, ...getFieldTooltip(this.props.instance))}
363
+ onMouseLeave={(e) => tooltipMouseLeave(e, ...getFieldTooltip(this.props.instance))}
364
+ />
365
+ {icon}
366
+ {insideButton}
367
+ {dropdown}
368
+ {label}
369
+ {help}
370
+ </div>
371
+ );
372
+ }
373
+
374
+ onMouseDown(e) {
375
+ e.stopPropagation();
376
+
377
+ if (this.state.dropdownOpen) this.closeDropdown(e);
378
+ else {
379
+ this.openDropdownOnFocus = true;
380
+ }
381
+
382
+ //icon click
383
+ if (e.target != this.input) {
384
+ e.preventDefault();
385
+ if (!this.state.dropdownOpen) this.openDropdown(e);
386
+ else this.input.focus();
387
+ }
388
+ }
389
+
390
+ onFocus(e) {
391
+ let { instance } = this.props;
392
+ let { widget } = instance;
393
+ if (widget.trackFocus) {
394
+ this.setState({
395
+ focus: true,
396
+ });
397
+ }
398
+ if (this.openDropdownOnFocus) this.openDropdown(e);
399
+ }
400
+
401
+ onKeyDown(e) {
402
+ let { instance } = this.props;
403
+ if (instance.widget.handleKeyDown(e, instance) === false) return;
404
+
405
+ switch (e.keyCode) {
406
+ case KeyCode.enter:
407
+ e.stopPropagation();
408
+ this.onChange(e.target.value, "enter");
409
+ break;
410
+
411
+ case KeyCode.esc:
412
+ if (this.state.dropdownOpen) {
413
+ e.stopPropagation();
414
+ this.closeDropdown(e, () => {
415
+ this.input.focus();
416
+ });
417
+ }
418
+ break;
419
+
420
+ case KeyCode.left:
421
+ case KeyCode.right:
422
+ e.stopPropagation();
423
+ break;
424
+
425
+ case KeyCode.down:
426
+ this.openDropdown(e);
427
+ e.stopPropagation();
428
+ e.preventDefault();
429
+ break;
430
+ }
431
+ }
432
+
433
+ onBlur(e) {
434
+ if (!this.state.dropdownOpen) this.props.instance.setState({ visited: true });
435
+
436
+ if (this.state.focus)
437
+ this.setState({
438
+ focus: false,
439
+ });
440
+ this.onChange(e.target.value, "blur");
441
+ }
442
+
443
+ closeDropdown(e, callback) {
444
+ if (this.state.dropdownOpen) {
445
+ if (this.scrollableParents)
446
+ this.scrollableParents.forEach((el) => {
447
+ el.removeEventListener("scroll", this.updateDropdownPosition);
448
+ });
449
+
450
+ this.props.instance.setState({ visited: true });
451
+ this.setState({ dropdownOpen: false }, callback);
452
+ } else if (callback) callback();
453
+ }
454
+
455
+ openDropdown(e) {
456
+ var { data } = this.props.instance;
457
+ this.openDropdownOnFocus = false;
458
+
459
+ if (!this.state.dropdownOpen && !(data.disabled || data.readOnly)) {
460
+ this.setState({ dropdownOpen: true });
461
+ }
462
+ }
463
+
464
+ onClearClick(e) {
465
+ e.stopPropagation();
466
+ e.preventDefault();
467
+
468
+ var { instance } = this.props;
469
+ var { widget } = instance;
470
+
471
+ widget.handleSelect(instance, null, null);
472
+ }
473
+
474
+ UNSAFE_componentWillReceiveProps(props) {
475
+ var { data, state } = props.instance;
476
+ if (data.formatted != this.input.value && (data.formatted != this.props.data.formatted || !state.inputError)) {
477
+ this.input.value = data.formatted || "";
478
+ props.instance.setState({
479
+ inputError: false,
480
+ });
481
+ }
482
+ tooltipParentWillReceiveProps(this.input, ...getFieldTooltip(this.props.instance));
483
+ }
484
+
485
+ componentDidMount() {
486
+ tooltipParentDidMount(this.input, ...getFieldTooltip(this.props.instance));
487
+ autoFocus(this.input, this);
488
+ }
489
+
490
+ componentDidUpdate() {
491
+ autoFocus(this.input, this);
492
+ }
493
+
494
+ componentWillUnmount() {
495
+ if (this.input == getActiveElement() && this.input.value != this.props.data.formatted) {
496
+ this.onChange(this.input.value, "blur");
497
+ }
498
+ tooltipParentWillUnmount(this.props.instance);
499
+ }
500
+
501
+ onChange(inputValue, eventType) {
502
+ var { instance } = this.props;
503
+ var { widget } = instance;
504
+
505
+ if (widget.reactOn.indexOf(eventType) == -1) return;
506
+
507
+ var parts = inputValue.split("-");
508
+ var date1 = widget.parseDate(parts[0]);
509
+ var date2 = widget.parseDate(parts[1]) || date1;
510
+
511
+ if ((date1 != null && isNaN(date1)) || (date2 != null && isNaN(date2))) {
512
+ instance.setState({
513
+ inputError: widget.inputErrorText,
514
+ });
515
+ } else if (eventType == "blur" || eventType == "enter") {
516
+ if (date2) date2 = new Date(date2.getFullYear(), date2.getMonth() + 1, 1);
517
+ instance.setState({
518
+ visited: true,
519
+ });
520
+ widget.handleSelect(instance, date1, date2);
521
+ }
522
+ }
523
+ }