cx 26.4.0 → 26.4.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (598) hide show
  1. package/.mocharc.json +5 -5
  2. package/LICENSE-THIRD-PARTY.md +91 -91
  3. package/LICENSE.md +7 -7
  4. package/README.md +46 -46
  5. package/build/charts/shapes.d.ts.map +1 -1
  6. package/build/charts/shapes.js +14 -7
  7. package/build/util/innerTextTrim.js +1 -1
  8. package/build/widgets/overlay/ContextMenu.d.ts.map +1 -1
  9. package/build/widgets/overlay/ContextMenu.js +1 -0
  10. package/build/widgets/overlay/Dropdown.d.ts +11 -3
  11. package/build/widgets/overlay/Dropdown.d.ts.map +1 -1
  12. package/build/widgets/overlay/Dropdown.js +52 -25
  13. package/build/widgets/overlay/Overlay.d.ts.map +1 -1
  14. package/build/widgets/overlay/Overlay.js +1 -1
  15. package/build.js +133 -133
  16. package/dist/charts.js +80 -45
  17. package/dist/manifest.js +806 -806
  18. package/dist/util.js +1 -1
  19. package/dist/widgets.css +13 -12
  20. package/dist/widgets.js +215 -85
  21. package/package.json +101 -100
  22. package/src/charts/Bar.scss +32 -32
  23. package/src/charts/Bar.ts +114 -114
  24. package/src/charts/BarGraph.tsx +145 -145
  25. package/src/charts/BubbleGraph.scss +35 -35
  26. package/src/charts/BubbleGraph.tsx +165 -165
  27. package/src/charts/Chart.ts +110 -110
  28. package/src/charts/ColorMap.ts +150 -150
  29. package/src/charts/Column.scss +32 -32
  30. package/src/charts/Column.ts +124 -124
  31. package/src/charts/ColumnBarBase.tsx +309 -309
  32. package/src/charts/ColumnBarGraphBase.tsx +229 -229
  33. package/src/charts/ColumnGraph.scss +32 -32
  34. package/src/charts/ColumnGraph.tsx +153 -153
  35. package/src/charts/Grid.ts +5 -5
  36. package/src/charts/Gridlines.scss +27 -27
  37. package/src/charts/Gridlines.tsx +88 -88
  38. package/src/charts/Legend.tsx +314 -314
  39. package/src/charts/LegendEntry.tsx +197 -197
  40. package/src/charts/LineGraph.tsx +455 -455
  41. package/src/charts/Marker.scss +46 -46
  42. package/src/charts/Marker.tsx +538 -538
  43. package/src/charts/MarkerLine.scss +23 -23
  44. package/src/charts/MarkerLine.tsx +233 -233
  45. package/src/charts/MouseTracker.tsx +115 -115
  46. package/src/charts/Pie.ts +8 -8
  47. package/src/charts/PieChart.scss +32 -32
  48. package/src/charts/PieChart.tsx +843 -843
  49. package/src/charts/PieLabel.tsx +124 -124
  50. package/src/charts/PieLabelsContainer.ts +68 -68
  51. package/src/charts/Range.scss +23 -23
  52. package/src/charts/Range.tsx +318 -318
  53. package/src/charts/RangeMarker.scss +17 -17
  54. package/src/charts/RangeMarker.tsx +250 -250
  55. package/src/charts/ScatterGraph.scss +27 -27
  56. package/src/charts/ScatterGraph.tsx +245 -245
  57. package/src/charts/Swimlane.scss +19 -19
  58. package/src/charts/Swimlane.tsx +195 -195
  59. package/src/charts/Swimlanes.scss +19 -19
  60. package/src/charts/Swimlanes.tsx +179 -179
  61. package/src/charts/axis/Axis.scss +24 -24
  62. package/src/charts/axis/Axis.tsx +444 -444
  63. package/src/charts/axis/CategoryAxis.scss +37 -37
  64. package/src/charts/axis/CategoryAxis.tsx +313 -313
  65. package/src/charts/axis/NumericAxis.scss +38 -38
  66. package/src/charts/axis/NumericAxis.tsx +437 -437
  67. package/src/charts/axis/Stack.ts +61 -61
  68. package/src/charts/axis/TimeAxis.scss +37 -37
  69. package/src/charts/axis/TimeAxis.tsx +834 -834
  70. package/src/charts/axis/index.scss +4 -4
  71. package/src/charts/axis/index.ts +3 -3
  72. package/src/charts/axis/variables.scss +5 -5
  73. package/src/charts/helpers/MinMaxFinder.ts +66 -66
  74. package/src/charts/helpers/PointReducer.ts +135 -135
  75. package/src/charts/helpers/index.ts +4 -4
  76. package/src/charts/index.scss +21 -21
  77. package/src/charts/index.ts +34 -34
  78. package/src/charts/palette.scss +77 -77
  79. package/src/charts/palette.variables.scss +23 -23
  80. package/src/charts/shapes.tsx +93 -86
  81. package/src/charts/variables.scss +53 -53
  82. package/src/core.d.ts +182 -182
  83. package/src/data/AggregateFunction.ts +171 -171
  84. package/src/data/ArrayElementView.spec.ts +88 -88
  85. package/src/data/ArrayElementView.ts +91 -91
  86. package/src/data/ArrayRef.ts +34 -34
  87. package/src/data/AugmentedViewBase.ts +88 -88
  88. package/src/data/Binding.spec.ts +69 -69
  89. package/src/data/Binding.ts +107 -107
  90. package/src/data/ExposedRecordView.ts +95 -95
  91. package/src/data/ExposedValueView.ts +89 -89
  92. package/src/data/Expression.spec.ts +229 -229
  93. package/src/data/Expression.ts +233 -233
  94. package/src/data/Grouper.spec.ts +57 -57
  95. package/src/data/Grouper.ts +158 -158
  96. package/src/data/NestedDataView.ts +43 -43
  97. package/src/data/ReadOnlyDataView.ts +39 -39
  98. package/src/data/Ref.spec.ts +79 -79
  99. package/src/data/Ref.ts +104 -104
  100. package/src/data/Selector.ts +10 -10
  101. package/src/data/Store.spec.ts +22 -22
  102. package/src/data/Store.ts +52 -52
  103. package/src/data/StoreProxy.ts +19 -19
  104. package/src/data/StoreRef.spec.ts +24 -24
  105. package/src/data/StoreRef.ts +66 -66
  106. package/src/data/StringTemplate.spec.ts +132 -132
  107. package/src/data/StringTemplate.ts +93 -93
  108. package/src/data/StructuredSelector.spec.ts +113 -113
  109. package/src/data/StructuredSelector.ts +146 -146
  110. package/src/data/SubscribableView.ts +63 -63
  111. package/src/data/View.spec.ts +60 -60
  112. package/src/data/View.ts +346 -346
  113. package/src/data/ZoomIntoPropertyView.spec.ts +64 -64
  114. package/src/data/ZoomIntoPropertyView.ts +45 -45
  115. package/src/data/comparer.spec.ts +60 -60
  116. package/src/data/comparer.ts +78 -78
  117. package/src/data/computable.spec.ts +87 -87
  118. package/src/data/computable.ts +69 -69
  119. package/src/data/createAccessorModelProxy.spec.tsx +157 -157
  120. package/src/data/createStructuredSelector.spec.ts +45 -45
  121. package/src/data/createStructuredSelector.ts +62 -62
  122. package/src/data/defaultCompare.ts +14 -14
  123. package/src/data/diff/diffArrays.ts +49 -49
  124. package/src/data/diff/diffs.spec.ts +49 -49
  125. package/src/data/diff/index.ts +1 -1
  126. package/src/data/enableFatArrowExpansion.ts +6 -6
  127. package/src/data/getAccessor.spec.ts +11 -11
  128. package/src/data/getAccessor.ts +74 -74
  129. package/src/data/getSelector.spec.ts +43 -43
  130. package/src/data/getSelector.ts +66 -66
  131. package/src/data/index.ts +30 -30
  132. package/src/data/isSelector.ts +26 -26
  133. package/src/data/ops/append.spec.ts +28 -28
  134. package/src/data/ops/append.ts +5 -5
  135. package/src/data/ops/filter.spec.ts +35 -35
  136. package/src/data/ops/filter.ts +9 -9
  137. package/src/data/ops/findTreeNode.spec.ts +23 -23
  138. package/src/data/ops/findTreeNode.ts +33 -33
  139. package/src/data/ops/findTreePath.ts +28 -28
  140. package/src/data/ops/index.ts +11 -11
  141. package/src/data/ops/insertElement.ts +3 -3
  142. package/src/data/ops/merge.spec.ts +27 -27
  143. package/src/data/ops/merge.ts +13 -13
  144. package/src/data/ops/moveElement.ts +21 -21
  145. package/src/data/ops/removeTreeNodes.spec.ts +37 -37
  146. package/src/data/ops/removeTreeNodes.ts +28 -28
  147. package/src/data/ops/updateArray.spec.ts +69 -69
  148. package/src/data/ops/updateArray.ts +31 -31
  149. package/src/data/ops/updateTree.spec.ts +54 -54
  150. package/src/data/ops/updateTree.ts +49 -49
  151. package/src/data/test-types.ts +7 -7
  152. package/src/hooks/createLocalStorageRef.ts +21 -21
  153. package/src/hooks/index.ts +8 -8
  154. package/src/hooks/invokeCallback.spec.tsx +59 -59
  155. package/src/hooks/invokeCallback.ts +8 -8
  156. package/src/hooks/resolveCallback.spec.tsx +71 -71
  157. package/src/hooks/resolveCallback.ts +16 -16
  158. package/src/hooks/store.spec.tsx +67 -67
  159. package/src/hooks/store.ts +46 -46
  160. package/src/hooks/useEffect.ts +14 -14
  161. package/src/hooks/useInterval.ts +8 -8
  162. package/src/hooks/useState.ts +20 -20
  163. package/src/hooks/useTrigger.spec.tsx +105 -105
  164. package/src/hooks/useTrigger.ts +26 -26
  165. package/src/index.scss +5 -5
  166. package/src/index.ts +7 -7
  167. package/src/jsx-dev-runtime.ts +4 -4
  168. package/src/jsx-runtime.spec.tsx +508 -508
  169. package/src/jsx-runtime.ts +88 -88
  170. package/src/locale/de-de.ts +76 -76
  171. package/src/locale/en-us.ts +75 -75
  172. package/src/locale/es-es.ts +76 -76
  173. package/src/locale/fr-fr.ts +76 -76
  174. package/src/locale/nl-nl.ts +76 -76
  175. package/src/locale/pt-pt.ts +76 -76
  176. package/src/locale/sr-latn-ba.ts +76 -76
  177. package/src/maps.scss +5 -5
  178. package/src/svg/BoundedObject.ts +101 -101
  179. package/src/svg/ClipRect.tsx +29 -29
  180. package/src/svg/Ellipse.tsx +76 -76
  181. package/src/svg/Line.tsx +73 -73
  182. package/src/svg/NonOverlappingRect.ts +26 -26
  183. package/src/svg/NonOverlappingRectGroup.ts +49 -49
  184. package/src/svg/Rectangle.tsx +98 -98
  185. package/src/svg/Svg.scss +28 -28
  186. package/src/svg/Svg.tsx +241 -241
  187. package/src/svg/Text.scss +19 -19
  188. package/src/svg/Text.tsx +190 -190
  189. package/src/svg/TextualBoundedObject.ts +35 -35
  190. package/src/svg/index.scss +10 -10
  191. package/src/svg/index.ts +17 -17
  192. package/src/svg/util/Rect.ts +105 -105
  193. package/src/ui/CSS.ts +87 -87
  194. package/src/ui/CSSHelper.ts +17 -17
  195. package/src/ui/Container.spec.ts +87 -87
  196. package/src/ui/Container.tsx +220 -220
  197. package/src/ui/ContentResolver.spec.tsx +585 -585
  198. package/src/ui/ContentResolver.ts +132 -132
  199. package/src/ui/Controller.spec.tsx +604 -604
  200. package/src/ui/Controller.ts +207 -207
  201. package/src/ui/Culture.ts +159 -159
  202. package/src/ui/Cx.spec.tsx +210 -210
  203. package/src/ui/Cx.tsx +386 -386
  204. package/src/ui/DataProxy.spec.tsx +337 -337
  205. package/src/ui/DataProxy.ts +55 -55
  206. package/src/ui/DetachedScope.tsx +159 -159
  207. package/src/ui/FocusManager.ts +171 -171
  208. package/src/ui/Format.ts +108 -108
  209. package/src/ui/HoverSync.tsx +221 -221
  210. package/src/ui/Instance.ts +963 -963
  211. package/src/ui/IsolatedScope.spec.tsx +62 -62
  212. package/src/ui/IsolatedScope.ts +52 -52
  213. package/src/ui/Localization.ts +70 -70
  214. package/src/ui/Prop.ts +141 -141
  215. package/src/ui/PureContainer.spec.tsx +232 -232
  216. package/src/ui/PureContainer.tsx +19 -19
  217. package/src/ui/RenderingContext.ts +99 -99
  218. package/src/ui/Repeater.ts +194 -194
  219. package/src/ui/Rescope.spec.tsx +199 -199
  220. package/src/ui/Rescope.ts +50 -50
  221. package/src/ui/ResizeManager.ts +30 -30
  222. package/src/ui/Restate.spec.tsx +422 -422
  223. package/src/ui/Restate.tsx +217 -217
  224. package/src/ui/StaticText.ts +11 -11
  225. package/src/ui/StructuredInstanceDataAccessor.ts +32 -32
  226. package/src/ui/Text.ts +52 -52
  227. package/src/ui/VDOM.ts +1 -1
  228. package/src/ui/Widget.spec.tsx +53 -53
  229. package/src/ui/Widget.tsx +359 -359
  230. package/src/ui/ZIndexManager.ts +11 -11
  231. package/src/ui/adapter/ArrayAdapter.spec.ts +55 -55
  232. package/src/ui/adapter/ArrayAdapter.ts +229 -229
  233. package/src/ui/adapter/DataAdapter.ts +52 -52
  234. package/src/ui/adapter/GroupAdapter.spec.ts +42 -42
  235. package/src/ui/adapter/GroupAdapter.ts +244 -244
  236. package/src/ui/adapter/TreeAdapter.spec.ts +76 -76
  237. package/src/ui/adapter/TreeAdapter.ts +245 -245
  238. package/src/ui/adapter/index.ts +4 -4
  239. package/src/ui/app/History.ts +147 -147
  240. package/src/ui/app/Url.spec.ts +50 -50
  241. package/src/ui/app/Url.ts +102 -102
  242. package/src/ui/app/index.ts +5 -5
  243. package/src/ui/app/startAppLoop.tsx +82 -82
  244. package/src/ui/app/startHotAppLoop.ts +41 -41
  245. package/src/ui/batchUpdates.ts +72 -72
  246. package/src/ui/bind.ts +10 -10
  247. package/src/ui/createFunctionalComponent.spec.tsx +605 -605
  248. package/src/ui/createFunctionalComponent.ts +96 -96
  249. package/src/ui/expr.ts +47 -47
  250. package/src/ui/exprHelpers.spec.ts +412 -412
  251. package/src/ui/exprHelpers.ts +96 -96
  252. package/src/ui/flattenProps.ts +21 -21
  253. package/src/ui/index.scss +2 -2
  254. package/src/ui/index.ts +51 -51
  255. package/src/ui/keyboardShortcuts.ts +50 -50
  256. package/src/ui/layout/Content.ts +30 -30
  257. package/src/ui/layout/ContentPlaceholder.spec.tsx +599 -599
  258. package/src/ui/layout/ContentPlaceholder.ts +133 -133
  259. package/src/ui/layout/FirstVisibleChildLayout.spec.tsx +207 -207
  260. package/src/ui/layout/FirstVisibleChildLayout.ts +60 -60
  261. package/src/ui/layout/LabelsLeftLayout.scss +45 -45
  262. package/src/ui/layout/LabelsLeftLayout.tsx +76 -76
  263. package/src/ui/layout/LabelsTopLayout.scss +73 -73
  264. package/src/ui/layout/LabelsTopLayout.tsx +156 -156
  265. package/src/ui/layout/UseParentLayout.ts +8 -8
  266. package/src/ui/layout/exploreChildren.ts +38 -38
  267. package/src/ui/layout/index.scss +2 -2
  268. package/src/ui/layout/index.ts +10 -10
  269. package/src/ui/layout/variables.scss +2 -2
  270. package/src/ui/selection/KeySelection.ts +153 -153
  271. package/src/ui/selection/PropertySelection.ts +87 -87
  272. package/src/ui/selection/Selection.ts +128 -128
  273. package/src/ui/selection/index.ts +3 -3
  274. package/src/ui/tpl.spec.ts +69 -69
  275. package/src/ui/tpl.ts +17 -17
  276. package/src/ui/variables.scss +1 -1
  277. package/src/util/Component.spec.ts +494 -494
  278. package/src/util/Component.ts +352 -352
  279. package/src/util/Console.ts +13 -13
  280. package/src/util/DOM.ts +88 -88
  281. package/src/util/Debug.ts +71 -71
  282. package/src/util/Format.spec.ts +69 -69
  283. package/src/util/Format.ts +267 -267
  284. package/src/util/GlobalCacheIdentifier.ts +11 -11
  285. package/src/util/KeyCode.ts +21 -21
  286. package/src/util/SubscriberList.ts +86 -86
  287. package/src/util/Timing.ts +58 -58
  288. package/src/util/TraversalStack.spec.ts +53 -53
  289. package/src/util/TraversalStack.ts +47 -47
  290. package/src/util/addEventListenerWithOptions.ts +41 -41
  291. package/src/util/browserSupportsPassiveEventHandlers.ts +20 -20
  292. package/src/util/calculateNaturalElementHeight.ts +22 -22
  293. package/src/util/call-once.scss +6 -6
  294. package/src/util/capitalize.ts +4 -4
  295. package/src/util/coalesce.ts +6 -6
  296. package/src/util/color/hslToRgb.ts +34 -34
  297. package/src/util/color/index.ts +4 -4
  298. package/src/util/color/parseColor.ts +173 -173
  299. package/src/util/color/rgbToHex.ts +14 -14
  300. package/src/util/color/rgbToHsl.ts +35 -35
  301. package/src/util/date/dateDiff.ts +9 -9
  302. package/src/util/date/diff.ts +13 -13
  303. package/src/util/date/encodeDate.ts +8 -8
  304. package/src/util/date/encodeDateWithTimezoneOffset.ts +18 -18
  305. package/src/util/date/index.ts +11 -11
  306. package/src/util/date/lowerBoundCheck.ts +13 -13
  307. package/src/util/date/maxDate.ts +14 -14
  308. package/src/util/date/minDate.ts +14 -14
  309. package/src/util/date/monthStart.ts +8 -8
  310. package/src/util/date/parseDateInvariant.ts +20 -20
  311. package/src/util/date/sameDate.ts +11 -11
  312. package/src/util/date/upperBoundCheck.spec.ts +30 -30
  313. package/src/util/date/upperBoundCheck.ts +13 -13
  314. package/src/util/date/zeroTime.ts +9 -9
  315. package/src/util/debounce.ts +26 -26
  316. package/src/util/dummyCallback.ts +1 -1
  317. package/src/util/escapeSpecialRegexCharacters.ts +8 -8
  318. package/src/util/eventCallbacks.ts +12 -12
  319. package/src/util/expandFatArrows.ts +118 -118
  320. package/src/util/findScrollableParent.ts +15 -15
  321. package/src/util/getActiveElement.ts +4 -4
  322. package/src/util/getParentFrameBoundingClientRect.ts +13 -13
  323. package/src/util/getScrollerBoundingClientRect.ts +12 -12
  324. package/src/util/getSearchQueryPredicate.spec.ts +40 -40
  325. package/src/util/getSearchQueryPredicate.ts +58 -58
  326. package/src/util/getTopLevelBoundingClientRect.ts +7 -7
  327. package/src/util/getVendorPrefix.ts +38 -38
  328. package/src/util/hasKey.ts +18 -18
  329. package/src/util/index.scss +13 -13
  330. package/src/util/index.ts +55 -55
  331. package/src/util/innerTextTrim.ts +10 -10
  332. package/src/util/isArray.ts +3 -3
  333. package/src/util/isDataRecord.ts +5 -5
  334. package/src/util/isDefined.ts +3 -3
  335. package/src/util/isDigit.ts +8 -8
  336. package/src/util/isFunction.ts +3 -3
  337. package/src/util/isNonEmptyArray.ts +3 -3
  338. package/src/util/isNumber.ts +3 -3
  339. package/src/util/isObject.ts +3 -3
  340. package/src/util/isPromise.ts +6 -6
  341. package/src/util/isString.ts +3 -3
  342. package/src/util/isTextInputElement.ts +2 -2
  343. package/src/util/isTouchDevice.ts +7 -7
  344. package/src/util/isTouchEvent.ts +64 -64
  345. package/src/util/isUndefined.ts +3 -3
  346. package/src/util/isValidIdentifierName.spec.ts +33 -33
  347. package/src/util/isValidIdentifierName.ts +5 -5
  348. package/src/util/maps.scss +1 -1
  349. package/src/util/onIdleCallback.ts +10 -10
  350. package/src/util/parseStyle.ts +29 -29
  351. package/src/util/quote.ts +6 -6
  352. package/src/util/reverseSlice.ts +9 -9
  353. package/src/util/routeAppend.spec.ts +19 -19
  354. package/src/util/routeAppend.ts +15 -15
  355. package/src/util/scrollElementIntoView.ts +40 -40
  356. package/src/util/scss/add-rules.scss +40 -40
  357. package/src/util/scss/besm.scss +15 -15
  358. package/src/util/scss/calc.scss +137 -137
  359. package/src/util/scss/call-once.scss +12 -12
  360. package/src/util/scss/clockwise.scss +49 -49
  361. package/src/util/scss/colors.scss +9 -9
  362. package/src/util/scss/deep-get.scss +11 -11
  363. package/src/util/scss/deep-merge.scss +21 -21
  364. package/src/util/scss/defaults.scss +24 -24
  365. package/src/util/scss/divide.scss +3 -3
  366. package/src/util/scss/elements.scss +78 -78
  367. package/src/util/scss/global.scss +15 -15
  368. package/src/util/scss/index.scss +1 -1
  369. package/src/util/scss/maps.scss +2 -2
  370. package/src/util/scss/pad-size.scss +9 -9
  371. package/src/util/scss/padding.scss +6 -6
  372. package/src/util/scss/screen-size.scss +5 -5
  373. package/src/util/scss/variables.scss +6 -6
  374. package/src/util/shallowEquals.ts +43 -43
  375. package/src/util/test/createTestRenderer.tsx +19 -19
  376. package/src/util/throttle.ts +21 -21
  377. package/src/util/validatedDebounce.ts +23 -23
  378. package/src/util/variables.scss +1 -1
  379. package/src/variables.scss +5 -5
  380. package/src/widgets/AccessorBindings.spec.tsx +90 -90
  381. package/src/widgets/Button.scss +143 -143
  382. package/src/widgets/Button.tsx +181 -181
  383. package/src/widgets/Button.variables.scss +21 -21
  384. package/src/widgets/CxCredit.scss +41 -41
  385. package/src/widgets/CxCredit.tsx +46 -46
  386. package/src/widgets/DocumentTitle.ts +95 -95
  387. package/src/widgets/FlexBox.scss +153 -153
  388. package/src/widgets/FlexBox.tsx +168 -168
  389. package/src/widgets/Heading.scss +46 -46
  390. package/src/widgets/Heading.ts +42 -42
  391. package/src/widgets/HighlightedSearchText.scss +27 -27
  392. package/src/widgets/HighlightedSearchText.tsx +54 -54
  393. package/src/widgets/HtmlElement.spec.helpers.tsx +108 -108
  394. package/src/widgets/HtmlElement.spec.tsx +117 -117
  395. package/src/widgets/HtmlElement.tsx +468 -468
  396. package/src/widgets/Icon.scss +28 -28
  397. package/src/widgets/Icon.ts +64 -64
  398. package/src/widgets/List.scss +100 -100
  399. package/src/widgets/List.tsx +792 -792
  400. package/src/widgets/ProgressBar.scss +64 -64
  401. package/src/widgets/ProgressBar.tsx +66 -66
  402. package/src/widgets/ReactElementWrapper.spec.tsx +452 -452
  403. package/src/widgets/ReactElementWrapper.tsx +108 -108
  404. package/src/widgets/Resizer.scss +46 -46
  405. package/src/widgets/Resizer.tsx +200 -200
  406. package/src/widgets/Sandbox.ts +104 -104
  407. package/src/widgets/Section.scss +53 -53
  408. package/src/widgets/Section.tsx +187 -187
  409. package/src/widgets/animations.scss +13 -13
  410. package/src/widgets/autoFocus.ts +9 -9
  411. package/src/widgets/box.scss +47 -47
  412. package/src/widgets/cx.ts +63 -63
  413. package/src/widgets/drag-drop/DragClone.scss +44 -44
  414. package/src/widgets/drag-drop/DragHandle.scss +25 -25
  415. package/src/widgets/drag-drop/DragHandle.tsx +47 -47
  416. package/src/widgets/drag-drop/DragSource.scss +33 -33
  417. package/src/widgets/drag-drop/DragSource.tsx +237 -237
  418. package/src/widgets/drag-drop/DropZone.scss +83 -83
  419. package/src/widgets/drag-drop/DropZone.tsx +353 -353
  420. package/src/widgets/drag-drop/index.scss +4 -4
  421. package/src/widgets/drag-drop/index.ts +4 -4
  422. package/src/widgets/drag-drop/maps.scss +7 -7
  423. package/src/widgets/drag-drop/ops.tsx +422 -422
  424. package/src/widgets/drag-drop/variables.scss +17 -17
  425. package/src/widgets/enableAllInternalDependencies.ts +11 -11
  426. package/src/widgets/form/Calendar.maps.scss +54 -54
  427. package/src/widgets/form/Calendar.scss +237 -237
  428. package/src/widgets/form/Calendar.variables.scss +21 -21
  429. package/src/widgets/form/Checkbox.maps.scss +34 -34
  430. package/src/widgets/form/Checkbox.scss +140 -140
  431. package/src/widgets/form/Checkbox.tsx +340 -340
  432. package/src/widgets/form/Checkbox.variables.scss +8 -8
  433. package/src/widgets/form/ColorField.scss +112 -112
  434. package/src/widgets/form/ColorField.tsx +551 -551
  435. package/src/widgets/form/ColorPicker.maps.scss +21 -21
  436. package/src/widgets/form/ColorPicker.scss +302 -302
  437. package/src/widgets/form/ColorPicker.tsx +606 -606
  438. package/src/widgets/form/ColorPicker.variables.scss +9 -9
  439. package/src/widgets/form/DateField.ts +21 -21
  440. package/src/widgets/form/DateTimeField.scss +111 -111
  441. package/src/widgets/form/DateTimeField.tsx +814 -814
  442. package/src/widgets/form/DateTimePicker.scss +57 -57
  443. package/src/widgets/form/DateTimePicker.tsx +429 -429
  444. package/src/widgets/form/Field.maps.scss +122 -122
  445. package/src/widgets/form/Field.scss +200 -200
  446. package/src/widgets/form/Field.tsx +715 -715
  447. package/src/widgets/form/Field.variables.scss +46 -46
  448. package/src/widgets/form/FieldGroup.ts +10 -10
  449. package/src/widgets/form/FieldIcon.ts +61 -61
  450. package/src/widgets/form/HelpText.scss +27 -27
  451. package/src/widgets/form/HelpText.ts +15 -15
  452. package/src/widgets/form/Label.scss +45 -45
  453. package/src/widgets/form/Label.tsx +117 -117
  454. package/src/widgets/form/LabeledContainer.ts +77 -77
  455. package/src/widgets/form/LookupField.maps.scss +26 -26
  456. package/src/widgets/form/LookupField.spec.tsx +93 -93
  457. package/src/widgets/form/LookupField.tsx +1689 -1689
  458. package/src/widgets/form/MonthField.scss +113 -113
  459. package/src/widgets/form/MonthField.tsx +748 -748
  460. package/src/widgets/form/MonthPicker.maps.scss +50 -50
  461. package/src/widgets/form/MonthPicker.scss +134 -134
  462. package/src/widgets/form/MonthPicker.tsx +1061 -1061
  463. package/src/widgets/form/MonthPicker.variables.scss +24 -24
  464. package/src/widgets/form/NumberField.scss +72 -72
  465. package/src/widgets/form/NumberField.tsx +653 -653
  466. package/src/widgets/form/Radio.maps.scss +36 -36
  467. package/src/widgets/form/Radio.scss +133 -133
  468. package/src/widgets/form/Radio.tsx +249 -249
  469. package/src/widgets/form/Radio.variables.scss +7 -7
  470. package/src/widgets/form/Select.scss +104 -104
  471. package/src/widgets/form/Select.tsx +327 -327
  472. package/src/widgets/form/Slider.scss +130 -130
  473. package/src/widgets/form/Slider.tsx +473 -473
  474. package/src/widgets/form/Switch.scss +152 -152
  475. package/src/widgets/form/Switch.tsx +176 -176
  476. package/src/widgets/form/TextArea.scss +58 -58
  477. package/src/widgets/form/TextArea.tsx +229 -229
  478. package/src/widgets/form/TextField.scss +66 -66
  479. package/src/widgets/form/TextField.tsx +453 -453
  480. package/src/widgets/form/TimeField.ts +10 -10
  481. package/src/widgets/form/TimeList.tsx +105 -105
  482. package/src/widgets/form/UploadButton.scss +57 -57
  483. package/src/widgets/form/UploadButton.tsx +387 -387
  484. package/src/widgets/form/ValidationError.scss +27 -27
  485. package/src/widgets/form/ValidationError.tsx +81 -81
  486. package/src/widgets/form/ValidationGroup.spec.tsx +147 -147
  487. package/src/widgets/form/ValidationGroup.ts +141 -141
  488. package/src/widgets/form/Validator.ts +60 -60
  489. package/src/widgets/form/Wheel.scss +164 -164
  490. package/src/widgets/form/Wheel.tsx +309 -309
  491. package/src/widgets/form/index.scss +22 -22
  492. package/src/widgets/form/index.ts +28 -28
  493. package/src/widgets/form/maps.scss +81 -81
  494. package/src/widgets/form/variables.scss +114 -114
  495. package/src/widgets/grid/Grid.spec.ts +42 -42
  496. package/src/widgets/grid/Grid.tsx +4269 -4269
  497. package/src/widgets/grid/GridCell.ts +143 -143
  498. package/src/widgets/grid/GridCellEditor.tsx +58 -58
  499. package/src/widgets/grid/GridRow.ts +302 -302
  500. package/src/widgets/grid/GridRowLine.ts +49 -49
  501. package/src/widgets/grid/Pagination.scss +124 -124
  502. package/src/widgets/grid/Pagination.tsx +134 -134
  503. package/src/widgets/grid/TreeNode.scss +107 -107
  504. package/src/widgets/grid/TreeNode.tsx +165 -165
  505. package/src/widgets/grid/createGridCellEditor.tsx +18 -18
  506. package/src/widgets/grid/index.scss +3 -3
  507. package/src/widgets/grid/index.ts +16 -16
  508. package/src/widgets/grid/maps.scss +110 -110
  509. package/src/widgets/icons/arrow-down.svg +3 -3
  510. package/src/widgets/icons/arrow-right.svg +2 -2
  511. package/src/widgets/icons/base.svg +104 -104
  512. package/src/widgets/icons/calendar-old.svg +169 -169
  513. package/src/widgets/icons/calendar.svg +187 -187
  514. package/src/widgets/icons/calendar.tsx +22 -22
  515. package/src/widgets/icons/check.tsx +14 -14
  516. package/src/widgets/icons/clear.svg +74 -74
  517. package/src/widgets/icons/clear.tsx +16 -16
  518. package/src/widgets/icons/close.svg +74 -74
  519. package/src/widgets/icons/close.tsx +20 -20
  520. package/src/widgets/icons/cx.tsx +39 -39
  521. package/src/widgets/icons/drop-down.tsx +16 -16
  522. package/src/widgets/icons/dropdown-arrow.svg +61 -61
  523. package/src/widgets/icons/file.svg +4 -4
  524. package/src/widgets/icons/file.tsx +14 -14
  525. package/src/widgets/icons/folder-open.svg +5 -5
  526. package/src/widgets/icons/folder-open.tsx +16 -16
  527. package/src/widgets/icons/folder.svg +58 -58
  528. package/src/widgets/icons/folder.tsx +13 -13
  529. package/src/widgets/icons/forward.svg +67 -67
  530. package/src/widgets/icons/forward.tsx +26 -26
  531. package/src/widgets/icons/index.ts +14 -14
  532. package/src/widgets/icons/loading.svg +4 -4
  533. package/src/widgets/icons/loading.tsx +28 -28
  534. package/src/widgets/icons/menu.tsx +18 -18
  535. package/src/widgets/icons/pixel-picker.tsx +18 -18
  536. package/src/widgets/icons/registry.ts +56 -56
  537. package/src/widgets/icons/search.svg +107 -107
  538. package/src/widgets/icons/search.tsx +14 -14
  539. package/src/widgets/icons/sort-asc.svg +3 -3
  540. package/src/widgets/icons/sort-asc.tsx +15 -15
  541. package/src/widgets/icons/square.tsx +22 -22
  542. package/src/widgets/index.d.ts +55 -55
  543. package/src/widgets/index.scss +16 -16
  544. package/src/widgets/lists.scss +42 -42
  545. package/src/widgets/maps.scss +139 -139
  546. package/src/widgets/nav/Link.scss +33 -33
  547. package/src/widgets/nav/Link.ts +11 -11
  548. package/src/widgets/nav/LinkButton.ts +176 -176
  549. package/src/widgets/nav/Menu.scss +82 -82
  550. package/src/widgets/nav/Menu.tsx +497 -497
  551. package/src/widgets/nav/Menu.variables.scss +13 -13
  552. package/src/widgets/nav/MenuItem.tsx +525 -525
  553. package/src/widgets/nav/MenuSpacer.ts +18 -18
  554. package/src/widgets/nav/RedirectRoute.ts +49 -49
  555. package/src/widgets/nav/Route.spec.tsx +24 -24
  556. package/src/widgets/nav/Route.ts +142 -142
  557. package/src/widgets/nav/Scroller.scss +157 -157
  558. package/src/widgets/nav/Scroller.tsx +252 -252
  559. package/src/widgets/nav/Submenu.ts +6 -6
  560. package/src/widgets/nav/Tab.maps.scss +78 -78
  561. package/src/widgets/nav/Tab.scss +88 -88
  562. package/src/widgets/nav/Tab.variables.scss +15 -15
  563. package/src/widgets/nav/cover.scss +24 -24
  564. package/src/widgets/nav/index.scss +6 -6
  565. package/src/widgets/nav/index.ts +10 -10
  566. package/src/widgets/nav/maps.scss +32 -32
  567. package/src/widgets/nav/variables.scss +19 -19
  568. package/src/widgets/overlay/ContextMenu.ts +46 -45
  569. package/src/widgets/overlay/Dropdown.scss +218 -217
  570. package/src/widgets/overlay/Dropdown.tsx +980 -937
  571. package/src/widgets/overlay/FlyweightTooltipTracker.ts +91 -91
  572. package/src/widgets/overlay/MsgBox.tsx +155 -155
  573. package/src/widgets/overlay/Overlay.scss +88 -88
  574. package/src/widgets/overlay/Overlay.tsx +1 -0
  575. package/src/widgets/overlay/Toast.scss +172 -172
  576. package/src/widgets/overlay/Toast.ts +121 -121
  577. package/src/widgets/overlay/Tooltip.scss +108 -108
  578. package/src/widgets/overlay/Tooltip.tsx +460 -460
  579. package/src/widgets/overlay/Window.maps.scss +51 -51
  580. package/src/widgets/overlay/Window.scss +129 -129
  581. package/src/widgets/overlay/Window.tsx +320 -320
  582. package/src/widgets/overlay/Window.variables.scss +21 -21
  583. package/src/widgets/overlay/alerts.ts +46 -46
  584. package/src/widgets/overlay/captureMouse.scss +13 -13
  585. package/src/widgets/overlay/captureMouse.ts +195 -195
  586. package/src/widgets/overlay/createHotPromiseWindowFactory.ts +71 -71
  587. package/src/widgets/overlay/index.d.ts +11 -11
  588. package/src/widgets/overlay/index.scss +6 -6
  589. package/src/widgets/overlay/index.ts +11 -11
  590. package/src/widgets/overlay/maps.scss +44 -44
  591. package/src/widgets/overlay/tooltip-ops.ts +173 -173
  592. package/src/widgets/overlay/variables.scss +53 -53
  593. package/tsconfig.compile.json +4 -4
  594. package/tsconfig.json +35 -35
  595. package/tsconfig.mocha.json +14 -14
  596. package/build/widgets/form/Test.d.ts +0 -4
  597. package/build/widgets/form/Test.d.ts.map +0 -1
  598. package/build/widgets/form/Test.js +0 -5
@@ -1,1061 +1,1061 @@
1
- /**@jsxImportSource react */
2
-
3
- import { Widget, VDOM } from "../../ui/Widget";
4
- import { Field, getFieldTooltip, FieldInstance, FieldConfig } from "./Field";
5
- import { Culture } from "../../ui/Culture";
6
- import {
7
- FocusManager,
8
- oneFocusOut,
9
- offFocusOut,
10
- preventFocusOnTouch,
11
- } from "../../ui/FocusManager";
12
- import { StringTemplate } from "../../data/StringTemplate";
13
- import { monthStart } from "../../util/date/monthStart";
14
- import { dateDiff } from "../../util/date/dateDiff";
15
- import { minDate } from "../../util/date/minDate";
16
- import { maxDate } from "../../util/date/maxDate";
17
- import { lowerBoundCheck } from "../../util/date/lowerBoundCheck";
18
- import { upperBoundCheck } from "../../util/date/upperBoundCheck";
19
- import { Console } from "../../util/Console";
20
- import { KeyCode } from "../../util/KeyCode";
21
- import {
22
- tooltipParentWillReceiveProps,
23
- tooltipParentWillUnmount,
24
- tooltipMouseMove,
25
- tooltipMouseLeave,
26
- tooltipParentDidMount,
27
- } from "../overlay/tooltip-ops";
28
- import { Localization } from "../../ui/Localization";
29
- import { scrollElementIntoView } from "../../util/scrollElementIntoView";
30
- import { stopPropagation } from "../../util/eventCallbacks";
31
- import { isString } from "../../util/isString";
32
- import { isTouchEvent } from "../../util/isTouchEvent";
33
- import { getCursorPos } from "../overlay/captureMouse";
34
- import type { RenderingContext } from "../../ui/RenderingContext";
35
- import type { Instance } from "../../ui/Instance";
36
- import type { Prop, BooleanProp } from "../../ui/Prop";
37
-
38
- import { enableCultureSensitiveFormatting } from "../../ui/Format";
39
- import { parseDateInvariant } from "../../util";
40
- import { HtmlElement } from "../HtmlElement";
41
- enableCultureSensitiveFormatting();
42
-
43
- export class MonthPickerInstance<
44
- F extends MonthPicker = MonthPicker,
45
- > extends FieldInstance<F> {
46
- isMonthDateSelectable?: (monthDate: Date) => boolean;
47
- }
48
-
49
- export interface MonthPickerConfig extends FieldConfig {
50
- range?: BooleanProp;
51
- from?: Prop<string | Date>;
52
- to?: Prop<string | Date>;
53
- value?: Prop<string | Date>;
54
- refDate?: Prop<string | Date>;
55
- minValue?: Prop<string | Date>;
56
- minExclusive?: BooleanProp;
57
- maxValue?: Prop<string | Date>;
58
- maxExclusive?: BooleanProp;
59
- startYear?: number;
60
- endYear?: number;
61
- bufferSize?: number;
62
- maxValueErrorText?: string;
63
- maxExclusiveErrorText?: string;
64
- minValueErrorText?: string;
65
- minExclusiveErrorText?: string;
66
- encoding?: (date: Date) => any;
67
- inclusiveTo?: boolean;
68
- onBeforeSelect?: (
69
- e: Event,
70
- instance: MonthPickerInstance,
71
- dateFrom?: Date,
72
- dateTo?: Date,
73
- ) => boolean;
74
- onSelect?: (
75
- instance: MonthPickerInstance,
76
- dateFrom?: Date,
77
- dateTo?: Date,
78
- ) => void;
79
- hideQuarters?: boolean;
80
- onCreateIsMonthDateSelectable?: (
81
- validationParams: Record<string, any>,
82
- instance: MonthPickerInstance,
83
- ) => (monthDate: Date) => boolean;
84
- handleSelect?: (
85
- e: React.MouseEvent,
86
- instance: MonthPickerInstance,
87
- dateFrom?: Date,
88
- dateTo?: Date,
89
- ) => void;
90
- onBlur?:
91
- | string
92
- | ((e: React.FocusEvent, instance: MonthPickerInstance) => void);
93
- onFocusOut?:
94
- | string
95
- | ((e: React.FocusEvent, instance: MonthPickerInstance) => void);
96
- autoFocus?: boolean;
97
- }
98
-
99
- export class MonthPicker<
100
- Config extends MonthPickerConfig = MonthPickerConfig,
101
- > extends Field<Config, MonthPickerInstance> {
102
- declare public baseClass: string;
103
- declare public mode?: string;
104
- declare public range?: BooleanProp;
105
- declare public from?: Prop<string | Date>;
106
- declare public to?: Prop<string | Date>;
107
- declare public value?: Prop<string | Date>;
108
- declare public refDate?: Prop<string | Date>;
109
- declare public minValue?: Prop<string | Date>;
110
- declare public minExclusive?: BooleanProp;
111
- declare public maxValue?: Prop<string | Date>;
112
- declare public maxExclusive?: BooleanProp;
113
- declare public startYear: number;
114
- declare public endYear: number;
115
- declare public bufferSize: number;
116
- declare public maxValueErrorText: string;
117
- declare public maxExclusiveErrorText: string;
118
- declare public minValueErrorText: string;
119
- declare public minExclusiveErrorText: string;
120
- declare public encoding?: (date: Date) => any;
121
- declare public inclusiveTo?: boolean;
122
- declare public onBeforeSelect?: (
123
- e: Event,
124
- instance: MonthPickerInstance,
125
- dateFrom?: Date,
126
- dateTo?: Date,
127
- ) => boolean;
128
- declare public onSelect?: (
129
- instance: MonthPickerInstance,
130
- dateFrom?: Date,
131
- dateTo?: Date,
132
- ) => void;
133
- declare public hideQuarters?: boolean;
134
- declare public onCreateIsMonthDateSelectable?: (
135
- validationParams: Record<string, any>,
136
- instance: MonthPickerInstance,
137
- ) => (monthDate: Date) => boolean;
138
- declare public onBlur?:
139
- | string
140
- | ((e: React.FocusEvent, instance: MonthPickerInstance) => void);
141
- declare public onFocusOut?:
142
- | string
143
- | ((e: React.FocusEvent, instance: MonthPickerInstance) => void);
144
-
145
- declareData(...args: Record<string, unknown>[]): void {
146
- let values: Record<string, unknown> = {};
147
-
148
- if (this.mode == "range") {
149
- this.range = true;
150
- this.mode = "edit";
151
- Console.warn(
152
- 'Please use the range flag on MonthPickers. Syntax mode="range" is deprecated.',
153
- this,
154
- );
155
- }
156
-
157
- if (this.range) {
158
- values = {
159
- from: null,
160
- to: null,
161
- };
162
- } else {
163
- values = {
164
- value: this.emptyValue,
165
- };
166
- }
167
-
168
- super.declareData(
169
- values,
170
- {
171
- refDate: undefined,
172
- disabled: undefined,
173
- minValue: undefined,
174
- minExclusive: undefined,
175
- maxValue: undefined,
176
- maxExclusive: undefined,
177
- },
178
- ...args,
179
- );
180
- }
181
-
182
- init(): void {
183
- super.init();
184
- }
185
-
186
- prepareData(context: RenderingContext, instance: MonthPickerInstance): void {
187
- let { data } = instance;
188
- data.stateMods = {
189
- disabled: data.disabled,
190
- };
191
-
192
- if (!this.range && data.value)
193
- data.date = monthStart(parseDateInvariant(data.value));
194
-
195
- if (this.range) {
196
- if (data.from) data.from = monthStart(parseDateInvariant(data.from));
197
-
198
- if (data.to) {
199
- let date = parseDateInvariant(data.to);
200
- if (this.inclusiveTo) date.setDate(date.getDate() + 1);
201
- data.to = monthStart(date);
202
- }
203
- }
204
-
205
- if (data.refDate)
206
- data.refDate = monthStart(parseDateInvariant(data.refDate));
207
-
208
- if (data.maxValue)
209
- data.maxValue = monthStart(parseDateInvariant(data.maxValue));
210
-
211
- if (data.minValue)
212
- data.minValue = monthStart(parseDateInvariant(data.minValue));
213
-
214
- if (this.onCreateIsMonthDateSelectable) {
215
- instance.isMonthDateSelectable = instance.invoke(
216
- "onCreateIsMonthDateSelectable",
217
- data.validationParams,
218
- instance,
219
- );
220
- }
221
-
222
- super.prepareData(context, instance);
223
- }
224
-
225
- validate(context: RenderingContext, instance: MonthPickerInstance): void {
226
- super.validate(context, instance);
227
- let { data } = instance;
228
- if (!data.error && data.date) {
229
- let d;
230
- if (data.maxValue) {
231
- d = dateDiff(data.date, data.maxValue);
232
- if (d > 0)
233
- data.error = StringTemplate.format(
234
- this.maxValueErrorText,
235
- data.maxValue,
236
- );
237
- else if (d == 0 && data.maxExclusive)
238
- data.error = StringTemplate.format(
239
- this.maxExclusiveErrorText,
240
- data.maxValue,
241
- );
242
- }
243
-
244
- if (data.minValue) {
245
- d = dateDiff(data.date, data.minValue);
246
- if (d < 0)
247
- data.error = StringTemplate.format(
248
- this.minValueErrorText,
249
- data.minValue,
250
- );
251
- else if (d == 0 && data.minExclusive)
252
- data.error = StringTemplate.format(
253
- this.minExclusiveErrorText,
254
- data.minValue,
255
- );
256
- }
257
- }
258
- }
259
-
260
- renderInput(
261
- context: RenderingContext,
262
- instance: MonthPickerInstance,
263
- key: string,
264
- ): React.ReactNode {
265
- return (
266
- <MonthPickerComponent
267
- key={key}
268
- instance={instance}
269
- onBlur={this.onBlur}
270
- onFocusOut={this.onFocusOut}
271
- onKeyDown={this.onKeyDown}
272
- autoFocus={this.autoFocus}
273
- />
274
- );
275
- }
276
-
277
- handleSelect(
278
- e: React.KeyboardEvent | React.MouseEvent | React.TouchEvent,
279
- instance: MonthPickerInstance,
280
- date1: Date,
281
- date2: Date,
282
- ): void {
283
- let { data, widget, isMonthDateSelectable } = instance;
284
- let encode = widget.encoding || Culture.getDefaultDateEncoding();
285
-
286
- if (data.disabled) return;
287
-
288
- if (isMonthDateSelectable && !isMonthDateSelectable(date1)) return;
289
- if (!dateSelectableCheck(date1, data)) return;
290
-
291
- if (
292
- this.onBeforeSelect &&
293
- instance.invoke("onBeforeSelect", e, instance, date1, date2) === false
294
- )
295
- return;
296
-
297
- if (this.range) {
298
- instance.set("from", encode(date1));
299
- let toDate = new Date(date2);
300
- if (this.inclusiveTo) toDate.setDate(toDate.getDate() - 1);
301
- instance.set("to", encode(toDate));
302
- } else instance.set("value", encode(date1));
303
-
304
- if (this.onSelect) instance.invoke("onSelect", instance, date1, date2);
305
- }
306
- }
307
-
308
- MonthPicker.prototype.baseClass = "monthpicker";
309
- MonthPicker.prototype.range = false;
310
- MonthPicker.prototype.startYear = 1980;
311
- MonthPicker.prototype.endYear = 2050;
312
- MonthPicker.prototype.bufferSize = 15;
313
- MonthPicker.prototype.hideQuarters = false;
314
-
315
- // Localization
316
- MonthPicker.prototype.maxValueErrorText = "Select {0:d} or before.";
317
- MonthPicker.prototype.maxExclusiveErrorText = "Select a date before {0:d}.";
318
- MonthPicker.prototype.minValueErrorText = "Select {0:d} or later.";
319
- MonthPicker.prototype.minExclusiveErrorText = "Select a date after {0:d}.";
320
- MonthPicker.prototype.inclusiveTo = false;
321
-
322
- Localization.registerPrototype("cx/widgets/MonthPicker", MonthPicker);
323
-
324
- Widget.alias("month-picker", MonthPicker);
325
-
326
- const dateSelectableCheck = (
327
- date: Date,
328
- data: Record<string, any>,
329
- ): boolean => {
330
- if (
331
- data.maxValue &&
332
- !upperBoundCheck(date, data.maxValue as Date, data.maxExclusive)
333
- )
334
- return false;
335
-
336
- if (
337
- data.minValue &&
338
- !lowerBoundCheck(date, data.minValue as Date, data.minExclusive)
339
- )
340
- return false;
341
-
342
- return true;
343
- };
344
-
345
- const monthNumber = (date: Date): number => {
346
- return date.getFullYear() * 12 + date.getMonth();
347
- };
348
-
349
- interface MonthPickerComponentProps {
350
- instance: MonthPickerInstance;
351
- onBlur?:
352
- | string
353
- | ((e: React.FocusEvent, instance: MonthPickerInstance) => void);
354
- onFocusOut?:
355
- | string
356
- | ((e: React.FocusEvent, instance: MonthPickerInstance) => void);
357
- onKeyDown?:
358
- | string
359
- | ((
360
- e: React.KeyboardEvent,
361
- instance: MonthPickerInstance,
362
- ) => boolean | void);
363
- autoFocus?: boolean;
364
- }
365
-
366
- interface MonthPickerComponentState {
367
- cursorYear: number;
368
- cursorMonth: number;
369
- cursorQuarter: number;
370
- column: string;
371
- start: number;
372
- end: number;
373
- state?: string;
374
- focused?: boolean;
375
- yearHeight?: number;
376
- }
377
-
378
- interface CursorInfo {
379
- column: string;
380
- cursorYear: number;
381
- cursorMonth: number;
382
- cursorQuarter: number;
383
- }
384
-
385
- export class MonthPickerComponent extends VDOM.Component<
386
- MonthPickerComponentProps,
387
- MonthPickerComponentState
388
- > {
389
- dom: {
390
- el?: HTMLDivElement | null;
391
- table?: HTMLTableElement | null;
392
- } = {};
393
- dragStartDates?: [Date, Date];
394
-
395
- constructor(props: MonthPickerComponentProps) {
396
- super(props);
397
- let { data, widget } = props.instance;
398
-
399
- let cursor = monthStart(
400
- data.refDate ? data.refDate : data.date || data.from || new Date(),
401
- );
402
-
403
- this.dom = {};
404
-
405
- this.state = {
406
- cursorYear: cursor.getFullYear(),
407
- cursorMonth: cursor.getMonth() + 1,
408
- cursorQuarter: cursor.getMonth() / 3,
409
- column: "M",
410
- start: widget.startYear,
411
- end: Math.min(widget.startYear + widget.bufferSize, widget.endYear),
412
- };
413
-
414
- this.handleMouseDown = this.handleMouseDown.bind(this);
415
- this.handleMouseUp = this.handleMouseUp.bind(this);
416
- this.handleMouseEnter = this.handleMouseEnter.bind(this);
417
- this.handleKeyPress = this.handleKeyPress.bind(this);
418
- this.handleTouchMove = this.handleTouchMove.bind(this);
419
- this.handleTouchEnd = this.handleTouchEnd.bind(this);
420
- }
421
-
422
- extractCursorInfo(el: HTMLElement): CursorInfo | false {
423
- const dataPoint = el.getAttribute("data-point");
424
- if (!dataPoint) return false;
425
- let parts = dataPoint.split("-");
426
- if (parts[0] != "Y") return false;
427
- let cursor: CursorInfo = {
428
- column: "Y",
429
- cursorYear: Number(parts[1]),
430
- cursorMonth: 1,
431
- cursorQuarter: 1,
432
- };
433
- if (parts.length == 4) {
434
- cursor.column = parts[2];
435
- if (cursor.column == "M") cursor.cursorMonth = Number(parts[3]);
436
- else cursor.cursorQuarter = Number(parts[3]);
437
- }
438
- return cursor;
439
- }
440
-
441
- moveCursor<K extends keyof MonthPickerComponentState>(
442
- e: React.KeyboardEvent | React.MouseEvent | React.TouchEvent,
443
- data: Pick<MonthPickerComponentState, K>,
444
- options: { ensureVisible?: boolean; select?: boolean } = {},
445
- ): void {
446
- //e.preventDefault();
447
- e.stopPropagation();
448
-
449
- if ("cursorYear" in data && data.cursorYear !== undefined) {
450
- let { startYear, endYear } = this.props.instance.widget;
451
- (data as any).cursorYear = Math.max(
452
- startYear,
453
- Math.min(endYear, data.cursorYear as number),
454
- );
455
- }
456
-
457
- if (Object.keys(data).every((k) => this.state[k as K] == (data as any)[k]))
458
- return;
459
-
460
- this.setState(data, () => {
461
- if (options.ensureVisible) {
462
- let index = this.state.cursorYear - this.state.start;
463
- let tbody = this.dom.table?.children?.[index];
464
- if (tbody) scrollElementIntoView(tbody);
465
- }
466
-
467
- if (options.select) {
468
- let { instance } = this.props;
469
- let { widget, data } = instance;
470
-
471
- let [cursorFromDate, cursorToDate] = this.getCursorDates();
472
- let originFromDate = cursorFromDate,
473
- originToDate = cursorToDate;
474
-
475
- if (widget.range) {
476
- if (data.from) originFromDate = data.from;
477
- if (data.to) originToDate = data.to;
478
- }
479
-
480
- widget.handleSelect(
481
- e,
482
- instance,
483
- minDate(originFromDate, cursorFromDate),
484
- maxDate(originToDate, cursorToDate),
485
- );
486
- }
487
- });
488
- }
489
-
490
- handleKeyPress(e: React.KeyboardEvent): void {
491
- let { instance } = this.props;
492
- let { widget } = this.props.instance;
493
- let { cursorMonth, cursorYear, cursorQuarter, column } = this.state;
494
- let select = widget.range && e.shiftKey;
495
-
496
- switch (e.keyCode) {
497
- case KeyCode.enter:
498
- // if (widget.range && e.shiftKey && !this.dragStartDates) {
499
- // this.handleMouseDown(e, {}, false);
500
- // } else {
501
- // this.handleMouseUp(e);
502
- // }
503
- this.handleMouseUp(e);
504
- e.preventDefault();
505
- e.stopPropagation();
506
- break;
507
-
508
- case KeyCode.left:
509
- if (column == "Y")
510
- this.moveCursor(
511
- e,
512
- {
513
- cursorQuarter: 3,
514
- cursorYear: cursorYear - 1,
515
- column: "Q",
516
- },
517
- { select },
518
- );
519
- else if (column == "Q")
520
- this.moveCursor(
521
- e,
522
- { cursorMonth: cursorQuarter * 4, column: "M" },
523
- { select },
524
- );
525
- else if (column == "M" && (cursorMonth - 1) % 3 == 0)
526
- this.moveCursor(e, { column: "Y" });
527
- else this.moveCursor(e, { cursorMonth: cursorMonth - 1 }, { select });
528
- break;
529
-
530
- case KeyCode.right:
531
- if (column == "Y")
532
- this.moveCursor(e, { cursorMonth: 1, column: "M" }, { select });
533
- else if (column == "Q")
534
- this.moveCursor(
535
- e,
536
- {
537
- column: "Y",
538
- cursorYear: cursorQuarter == 3 ? cursorYear + 1 : cursorYear,
539
- },
540
- { select },
541
- );
542
- else if (column == "M" && (cursorMonth - 1) % 3 == 2)
543
- this.moveCursor(
544
- e,
545
- {
546
- column: "Q",
547
- cursorQuarter: Math.floor((cursorMonth - 1) / 3),
548
- },
549
- { select },
550
- );
551
- else this.moveCursor(e, { cursorMonth: cursorMonth + 1 }, { select });
552
- break;
553
-
554
- case KeyCode.up:
555
- e.preventDefault(); // prevent scroll
556
- if (column == "Y")
557
- this.moveCursor(
558
- e,
559
- { cursorYear: cursorYear - 1 },
560
- { ensureVisible: true, select },
561
- );
562
- else if (column == "Q")
563
- this.moveCursor(
564
- e,
565
- {
566
- cursorQuarter: (cursorQuarter + 3) % 4,
567
- cursorYear: cursorQuarter == 0 ? cursorYear - 1 : cursorYear,
568
- },
569
- { ensureVisible: true, select },
570
- );
571
- else if (column == "M")
572
- if (cursorMonth > 3)
573
- this.moveCursor(
574
- e,
575
- { cursorMonth: cursorMonth - 3 },
576
- { ensureVisible: true, select },
577
- );
578
- else
579
- this.moveCursor(
580
- e,
581
- { cursorMonth: cursorMonth + 9, cursorYear: cursorYear - 1 },
582
- { ensureVisible: true, select },
583
- );
584
- break;
585
-
586
- case KeyCode.down:
587
- e.preventDefault(); // prevent scroll
588
- if (column == "Y")
589
- this.moveCursor(
590
- e,
591
- { cursorYear: cursorYear + 1 },
592
- { ensureVisible: true, select },
593
- );
594
- else if (column == "Q")
595
- this.moveCursor(
596
- e,
597
- {
598
- cursorQuarter: (cursorQuarter + 1) % 4,
599
- cursorYear: cursorQuarter == 3 ? cursorYear + 1 : cursorYear,
600
- },
601
- { ensureVisible: true, select },
602
- );
603
- else if (column == "M")
604
- if (cursorMonth < 10)
605
- this.moveCursor(
606
- e,
607
- { cursorMonth: cursorMonth + 3 },
608
- { ensureVisible: true, select },
609
- );
610
- else
611
- this.moveCursor(
612
- e,
613
- { cursorMonth: cursorMonth - 9, cursorYear: cursorYear + 1 },
614
- { ensureVisible: true, select },
615
- );
616
- break;
617
-
618
- case KeyCode.pageUp:
619
- this.moveCursor(
620
- e,
621
- { cursorYear: this.state.cursorYear - 1 },
622
- { ensureVisible: true, select },
623
- );
624
- break;
625
-
626
- case KeyCode.pageDown:
627
- this.moveCursor(
628
- e,
629
- { cursorYear: this.state.cursorYear + 1 },
630
- { ensureVisible: true, select },
631
- );
632
- break;
633
-
634
- default:
635
- if (widget.onKeyDown) instance.invoke("onKeyDown", e, instance);
636
- break;
637
- }
638
- }
639
-
640
- handleBlur(e: React.FocusEvent): void {
641
- FocusManager.nudge();
642
- let { instance } = this.props;
643
- let { widget } = instance;
644
- if (widget.onBlur) instance.invoke("onBlur", e, instance);
645
- this.setState({
646
- focused: false,
647
- });
648
- }
649
-
650
- handleFocus(e: React.FocusEvent): void {
651
- this.setState({
652
- focused: true,
653
- });
654
- if (this.props.onFocusOut && this.dom.el)
655
- oneFocusOut(this, this.dom.el, this.handleFocusOut.bind(this));
656
- }
657
-
658
- handleFocusOut(e: React.FocusEvent): void {
659
- let { instance } = this.props;
660
- let { widget } = instance;
661
- if (widget.onFocusOut) instance.invoke("onFocusOut", e, instance);
662
- }
663
-
664
- getCursorDates(cursor?: CursorInfo): [Date, Date] {
665
- let { cursorMonth, cursorYear, cursorQuarter, column } =
666
- cursor ?? this.state;
667
- switch (column) {
668
- case "M":
669
- return [
670
- new Date(cursorYear, cursorMonth - 1, 1),
671
- new Date(cursorYear, cursorMonth, 1),
672
- ];
673
-
674
- case "Q":
675
- return [
676
- new Date(cursorYear, cursorQuarter * 3, 1),
677
- new Date(cursorYear, cursorQuarter * 3 + 3, 1),
678
- ];
679
-
680
- default:
681
- case "Y":
682
- return [new Date(cursorYear, 0, 1), new Date(cursorYear + 1, 0, 1)];
683
- }
684
- }
685
-
686
- handleTouchMove(e: React.TouchEvent): void {
687
- let cursorPos = getCursorPos(e);
688
- let el = document.elementFromPoint(cursorPos.clientX, cursorPos.clientY);
689
- if (
690
- this.dom.table &&
691
- el &&
692
- this.dom.table.contains(el) &&
693
- el instanceof HTMLElement &&
694
- isString(el.dataset.point)
695
- ) {
696
- let cursor = this.extractCursorInfo(el);
697
- if (cursor) this.moveCursor(e, cursor);
698
- }
699
- }
700
-
701
- handleTouchEnd(e: React.TouchEvent): void {
702
- if (this.state.state == "drag") this.handleMouseUp(e);
703
- }
704
-
705
- handleMouseEnter(e: React.MouseEvent): void {
706
- if (this.state.state != "drag") return;
707
- let cursor = this.extractCursorInfo(e.target as HTMLElement);
708
- if (cursor) {
709
- this.moveCursor(e, cursor);
710
- }
711
- }
712
-
713
- handleMouseDown(
714
- e: React.MouseEvent | React.TouchEvent,
715
- cursor?: CursorInfo | false,
716
- drag: boolean = true,
717
- ): void {
718
- let { instance } = this.props;
719
- let { widget } = instance;
720
-
721
- if (!cursor) {
722
- cursor = this.extractCursorInfo(e.currentTarget as HTMLElement);
723
- if (!cursor) return;
724
- this.moveCursor(e, cursor, { select: !widget.range });
725
- }
726
-
727
- e.stopPropagation();
728
- preventFocusOnTouch(e);
729
-
730
- this.dragStartDates = this.getCursorDates(cursor);
731
- if (drag) {
732
- this.setState({
733
- state: "drag",
734
- ...cursor,
735
- });
736
- }
737
- }
738
-
739
- handleMouseUp(
740
- e: React.KeyboardEvent | React.MouseEvent | React.TouchEvent,
741
- ): void {
742
- let { instance } = this.props;
743
- let { widget, data } = instance;
744
-
745
- e.stopPropagation();
746
- e.preventDefault();
747
-
748
- let [cursorFromDate, cursorToDate] = this.getCursorDates();
749
- let originFromDate = cursorFromDate,
750
- originToDate = cursorToDate;
751
- if (widget.range && e.shiftKey) {
752
- if (data.from) originFromDate = data.from;
753
- if (data.to) originToDate = data.to;
754
- } else if (this.state.state == "drag") {
755
- if (widget.range) {
756
- [originFromDate, originToDate] = this.dragStartDates!;
757
- }
758
- this.setState({ state: "normal" });
759
- } else {
760
- //skip mouse events originated somewhere else
761
- if (e.type != "keydown") return;
762
- }
763
- widget.handleSelect(
764
- e,
765
- instance,
766
- minDate(originFromDate, cursorFromDate),
767
- maxDate(originToDate, cursorToDate),
768
- );
769
- }
770
-
771
- render(): React.ReactNode {
772
- let { instance } = this.props;
773
- let { data, widget, isMonthDateSelectable } = instance;
774
- let { CSS, baseClass, startYear, endYear, hideQuarters } = widget;
775
-
776
- let years = [];
777
-
778
- let { start, end } = this.state;
779
-
780
- let from = 10000,
781
- to = 0,
782
- a,
783
- b;
784
-
785
- if (data.date && !widget.range) {
786
- from = monthNumber(data.date);
787
- to = from + 0.1;
788
- } else if (widget.range) {
789
- if (this.state.state == "drag") {
790
- let [originFromDate, originToDate] = this.dragStartDates!;
791
- let [cursorFromDate, cursorToDate] = this.getCursorDates();
792
- a = Math.min(monthNumber(originFromDate), monthNumber(cursorFromDate));
793
- b = Math.max(monthNumber(originToDate), monthNumber(cursorToDate));
794
- from = Math.min(a, b);
795
- to = Math.max(a, b);
796
- } else if (data.from && data.to) {
797
- a = monthNumber(data.from);
798
- b = monthNumber(data.to);
799
- from = Math.min(a, b);
800
- to = Math.max(a, b);
801
- }
802
- }
803
-
804
- let monthNames = Culture.getDateTimeCulture().getMonthNames("short");
805
- let showCursor = this.state.focused;
806
-
807
- for (let y = start; y <= end; y++) {
808
- let selectableMonths = 0b111111111111;
809
- // Loop through the months in a year to check if all months are unselectable
810
- for (let i = 0; i < 12; i++) {
811
- if (
812
- (isMonthDateSelectable &&
813
- !isMonthDateSelectable(new Date(y, i, 1))) ||
814
- !dateSelectableCheck(new Date(y, i, 1), data)
815
- ) {
816
- // Set month as unselectable at specified bit
817
- selectableMonths &= ~(1 << i);
818
- }
819
- }
820
-
821
- // All bits are 0 - all months are unselectable
822
- const unselectableYear = selectableMonths === 0;
823
-
824
- let rows = [];
825
- for (let q = 0; q < 4; q++) {
826
- let row = [];
827
- if (q == 0) {
828
- row.push(
829
- <th
830
- key="year"
831
- rowSpan={4}
832
- data-point={`Y-${y}`}
833
- className={CSS.expand(
834
- CSS.element(baseClass, "year", {
835
- cursor:
836
- showCursor &&
837
- this.state.column == "Y" &&
838
- y == this.state.cursorYear,
839
- }),
840
- CSS.state({ unselectable: unselectableYear }),
841
- )}
842
- onMouseEnter={
843
- unselectableYear ? undefined : this.handleMouseEnter
844
- }
845
- onMouseDown={unselectableYear ? undefined : this.handleMouseDown}
846
- onMouseUp={unselectableYear ? undefined : this.handleMouseUp}
847
- >
848
- {y}
849
- </th>,
850
- );
851
- }
852
-
853
- for (let i = 0; i < 3; i++) {
854
- let m = q * 3 + i + 1;
855
- const unselectableMonth = (selectableMonths & (1 << (m - 1))) === 0;
856
- let mno = y * 12 + m - 1;
857
- let handle = true; //isTouchDevice(); //mno === from || mno === to - 1;
858
- row.push(
859
- <td
860
- key={`M${m}`}
861
- className={CSS.state({
862
- cursor:
863
- showCursor &&
864
- this.state.column == "M" &&
865
- y == this.state.cursorYear &&
866
- m == this.state.cursorMonth,
867
- handle,
868
- selected: mno >= from && mno < to,
869
- unselectable: unselectableMonth,
870
- })}
871
- data-point={`Y-${y}-M-${m}`}
872
- onMouseEnter={
873
- unselectableMonth ? undefined : this.handleMouseEnter
874
- }
875
- onMouseDown={unselectableMonth ? undefined : this.handleMouseDown}
876
- onMouseUp={unselectableMonth ? undefined : this.handleMouseUp}
877
- onTouchStart={
878
- unselectableMonth ? undefined : this.handleMouseDown
879
- }
880
- onTouchMove={unselectableMonth ? undefined : this.handleTouchMove}
881
- onTouchEnd={this.handleMouseUp}
882
- >
883
- {monthNames[m - 1].substr(0, 3)}
884
- </td>,
885
- );
886
- }
887
-
888
- if (!hideQuarters) {
889
- let unselectableQuarter = true;
890
- const start = q * 3;
891
- for (let i = start; i < start + 3; i++) {
892
- if ((selectableMonths & (1 << i)) !== 0) {
893
- // found a selectable month in a quarter
894
- unselectableQuarter = false;
895
- break;
896
- }
897
- }
898
-
899
- row.push(
900
- <th
901
- key={`q${q}`}
902
- className={CSS.state({
903
- cursor:
904
- showCursor &&
905
- this.state.column == "Q" &&
906
- y == this.state.cursorYear &&
907
- q == this.state.cursorQuarter,
908
- unselectable: unselectableQuarter,
909
- })}
910
- data-point={`Y-${y}-Q-${q}`}
911
- onMouseEnter={
912
- unselectableQuarter ? undefined : this.handleMouseEnter
913
- }
914
- onMouseDown={
915
- unselectableQuarter ? undefined : this.handleMouseDown
916
- }
917
- onMouseUp={unselectableQuarter ? undefined : this.handleMouseUp}
918
- >
919
- {`Q${q + 1}`}
920
- </th>,
921
- );
922
- }
923
-
924
- rows.push(row);
925
- }
926
- years.push(rows);
927
- }
928
-
929
- return (
930
- <div
931
- ref={(el) => {
932
- this.dom.el = el;
933
- }}
934
- className={data.classNames}
935
- style={data.style}
936
- tabIndex={data.disabled ? null : data.tabIndex || 0}
937
- onKeyDown={this.handleKeyPress}
938
- onMouseDown={stopPropagation}
939
- onMouseMove={(e) =>
940
- tooltipMouseMove(e, ...getFieldTooltip(this.props.instance))
941
- }
942
- onMouseLeave={this.handleMouseLeave.bind(this)}
943
- onFocus={(e) => this.handleFocus(e)}
944
- onBlur={this.handleBlur.bind(this)}
945
- onScroll={this.onScroll.bind(this)}
946
- >
947
- {this.state.yearHeight && (
948
- <div
949
- style={{
950
- height: `${(start - startYear) * this.state.yearHeight}px`,
951
- }}
952
- />
953
- )}
954
- <table
955
- ref={(el) => {
956
- this.dom.table = el;
957
- }}
958
- >
959
- {years.map((rows, y) => (
960
- <tbody key={start + y}>
961
- {rows.map((cells, i) => (
962
- <tr key={i}>{cells}</tr>
963
- ))}
964
- </tbody>
965
- ))}
966
- </table>
967
- {this.state.yearHeight && (
968
- <div
969
- style={{
970
- height: `${Math.max(0, endYear - end) * this.state.yearHeight}px`,
971
- }}
972
- />
973
- )}
974
- </div>
975
- );
976
- }
977
-
978
- onScroll(): void {
979
- if (!this.dom.el || !this.state.yearHeight) return;
980
- let { startYear, endYear, bufferSize } = this.props.instance.widget;
981
- let visibleItems = ceil5(
982
- Math.ceil(this.dom.el.offsetHeight / this.state.yearHeight),
983
- );
984
- let start = Math.max(
985
- startYear,
986
- startYear +
987
- floor5(Math.floor(this.dom.el.scrollTop / this.state.yearHeight)) -
988
- visibleItems,
989
- );
990
- if (start != this.state.start) {
991
- this.setState({
992
- start,
993
- end: Math.min(start + bufferSize, endYear),
994
- });
995
- }
996
- }
997
-
998
- handleMouseLeave(e: React.MouseEvent): void {
999
- tooltipMouseLeave(e, ...getFieldTooltip(this.props.instance));
1000
- }
1001
-
1002
- componentDidMount(): void {
1003
- //non-input, ok to focus on mobile
1004
- if (this.props.autoFocus && this.dom.el) this.dom.el.focus();
1005
-
1006
- if (this.dom.el && this.dom.table) {
1007
- tooltipParentDidMount(
1008
- this.dom.el,
1009
- ...getFieldTooltip(this.props.instance),
1010
- );
1011
- let yearHeight =
1012
- this.dom.table.scrollHeight /
1013
- (this.props.instance.widget.bufferSize + 1);
1014
- this.setState(
1015
- {
1016
- yearHeight: yearHeight,
1017
- },
1018
- () => {
1019
- let { widget, data } = this.props.instance;
1020
- let { startYear } = widget;
1021
- let yearCount = 1;
1022
- if (widget.range && data.from && data.to) {
1023
- yearCount = data.to.getFullYear() - data.from.getFullYear() + 1;
1024
- if (data.to.getMonth() == 0 && data.to.getDate() == 1) yearCount--;
1025
- }
1026
- if (this.dom.el && this.state.yearHeight) {
1027
- this.dom.el.scrollTop =
1028
- (this.state.cursorYear - startYear + yearCount / 2) *
1029
- this.state.yearHeight -
1030
- this.dom.el.offsetHeight / 2;
1031
- }
1032
- },
1033
- );
1034
- }
1035
- }
1036
-
1037
- UNSAFE_componentWillReceiveProps(props: MonthPickerComponentProps): void {
1038
- this.setState({
1039
- state: "normal",
1040
- });
1041
- if (this.dom.el) {
1042
- tooltipParentWillReceiveProps(
1043
- this.dom.el,
1044
- ...getFieldTooltip(props.instance),
1045
- );
1046
- }
1047
- }
1048
-
1049
- componentWillUnmount(): void {
1050
- offFocusOut(this);
1051
- tooltipParentWillUnmount(this.props.instance);
1052
- }
1053
- }
1054
-
1055
- function ceil5(x: number): number {
1056
- return Math.ceil(x / 5) * 5;
1057
- }
1058
-
1059
- function floor5(x: number): number {
1060
- return Math.floor(x / 5) * 5;
1061
- }
1
+ /**@jsxImportSource react */
2
+
3
+ import { Widget, VDOM } from "../../ui/Widget";
4
+ import { Field, getFieldTooltip, FieldInstance, FieldConfig } from "./Field";
5
+ import { Culture } from "../../ui/Culture";
6
+ import {
7
+ FocusManager,
8
+ oneFocusOut,
9
+ offFocusOut,
10
+ preventFocusOnTouch,
11
+ } from "../../ui/FocusManager";
12
+ import { StringTemplate } from "../../data/StringTemplate";
13
+ import { monthStart } from "../../util/date/monthStart";
14
+ import { dateDiff } from "../../util/date/dateDiff";
15
+ import { minDate } from "../../util/date/minDate";
16
+ import { maxDate } from "../../util/date/maxDate";
17
+ import { lowerBoundCheck } from "../../util/date/lowerBoundCheck";
18
+ import { upperBoundCheck } from "../../util/date/upperBoundCheck";
19
+ import { Console } from "../../util/Console";
20
+ import { KeyCode } from "../../util/KeyCode";
21
+ import {
22
+ tooltipParentWillReceiveProps,
23
+ tooltipParentWillUnmount,
24
+ tooltipMouseMove,
25
+ tooltipMouseLeave,
26
+ tooltipParentDidMount,
27
+ } from "../overlay/tooltip-ops";
28
+ import { Localization } from "../../ui/Localization";
29
+ import { scrollElementIntoView } from "../../util/scrollElementIntoView";
30
+ import { stopPropagation } from "../../util/eventCallbacks";
31
+ import { isString } from "../../util/isString";
32
+ import { isTouchEvent } from "../../util/isTouchEvent";
33
+ import { getCursorPos } from "../overlay/captureMouse";
34
+ import type { RenderingContext } from "../../ui/RenderingContext";
35
+ import type { Instance } from "../../ui/Instance";
36
+ import type { Prop, BooleanProp } from "../../ui/Prop";
37
+
38
+ import { enableCultureSensitiveFormatting } from "../../ui/Format";
39
+ import { parseDateInvariant } from "../../util";
40
+ import { HtmlElement } from "../HtmlElement";
41
+ enableCultureSensitiveFormatting();
42
+
43
+ export class MonthPickerInstance<
44
+ F extends MonthPicker = MonthPicker,
45
+ > extends FieldInstance<F> {
46
+ isMonthDateSelectable?: (monthDate: Date) => boolean;
47
+ }
48
+
49
+ export interface MonthPickerConfig extends FieldConfig {
50
+ range?: BooleanProp;
51
+ from?: Prop<string | Date>;
52
+ to?: Prop<string | Date>;
53
+ value?: Prop<string | Date>;
54
+ refDate?: Prop<string | Date>;
55
+ minValue?: Prop<string | Date>;
56
+ minExclusive?: BooleanProp;
57
+ maxValue?: Prop<string | Date>;
58
+ maxExclusive?: BooleanProp;
59
+ startYear?: number;
60
+ endYear?: number;
61
+ bufferSize?: number;
62
+ maxValueErrorText?: string;
63
+ maxExclusiveErrorText?: string;
64
+ minValueErrorText?: string;
65
+ minExclusiveErrorText?: string;
66
+ encoding?: (date: Date) => any;
67
+ inclusiveTo?: boolean;
68
+ onBeforeSelect?: (
69
+ e: Event,
70
+ instance: MonthPickerInstance,
71
+ dateFrom?: Date,
72
+ dateTo?: Date,
73
+ ) => boolean;
74
+ onSelect?: (
75
+ instance: MonthPickerInstance,
76
+ dateFrom?: Date,
77
+ dateTo?: Date,
78
+ ) => void;
79
+ hideQuarters?: boolean;
80
+ onCreateIsMonthDateSelectable?: (
81
+ validationParams: Record<string, any>,
82
+ instance: MonthPickerInstance,
83
+ ) => (monthDate: Date) => boolean;
84
+ handleSelect?: (
85
+ e: React.MouseEvent,
86
+ instance: MonthPickerInstance,
87
+ dateFrom?: Date,
88
+ dateTo?: Date,
89
+ ) => void;
90
+ onBlur?:
91
+ | string
92
+ | ((e: React.FocusEvent, instance: MonthPickerInstance) => void);
93
+ onFocusOut?:
94
+ | string
95
+ | ((e: React.FocusEvent, instance: MonthPickerInstance) => void);
96
+ autoFocus?: boolean;
97
+ }
98
+
99
+ export class MonthPicker<
100
+ Config extends MonthPickerConfig = MonthPickerConfig,
101
+ > extends Field<Config, MonthPickerInstance> {
102
+ declare public baseClass: string;
103
+ declare public mode?: string;
104
+ declare public range?: BooleanProp;
105
+ declare public from?: Prop<string | Date>;
106
+ declare public to?: Prop<string | Date>;
107
+ declare public value?: Prop<string | Date>;
108
+ declare public refDate?: Prop<string | Date>;
109
+ declare public minValue?: Prop<string | Date>;
110
+ declare public minExclusive?: BooleanProp;
111
+ declare public maxValue?: Prop<string | Date>;
112
+ declare public maxExclusive?: BooleanProp;
113
+ declare public startYear: number;
114
+ declare public endYear: number;
115
+ declare public bufferSize: number;
116
+ declare public maxValueErrorText: string;
117
+ declare public maxExclusiveErrorText: string;
118
+ declare public minValueErrorText: string;
119
+ declare public minExclusiveErrorText: string;
120
+ declare public encoding?: (date: Date) => any;
121
+ declare public inclusiveTo?: boolean;
122
+ declare public onBeforeSelect?: (
123
+ e: Event,
124
+ instance: MonthPickerInstance,
125
+ dateFrom?: Date,
126
+ dateTo?: Date,
127
+ ) => boolean;
128
+ declare public onSelect?: (
129
+ instance: MonthPickerInstance,
130
+ dateFrom?: Date,
131
+ dateTo?: Date,
132
+ ) => void;
133
+ declare public hideQuarters?: boolean;
134
+ declare public onCreateIsMonthDateSelectable?: (
135
+ validationParams: Record<string, any>,
136
+ instance: MonthPickerInstance,
137
+ ) => (monthDate: Date) => boolean;
138
+ declare public onBlur?:
139
+ | string
140
+ | ((e: React.FocusEvent, instance: MonthPickerInstance) => void);
141
+ declare public onFocusOut?:
142
+ | string
143
+ | ((e: React.FocusEvent, instance: MonthPickerInstance) => void);
144
+
145
+ declareData(...args: Record<string, unknown>[]): void {
146
+ let values: Record<string, unknown> = {};
147
+
148
+ if (this.mode == "range") {
149
+ this.range = true;
150
+ this.mode = "edit";
151
+ Console.warn(
152
+ 'Please use the range flag on MonthPickers. Syntax mode="range" is deprecated.',
153
+ this,
154
+ );
155
+ }
156
+
157
+ if (this.range) {
158
+ values = {
159
+ from: null,
160
+ to: null,
161
+ };
162
+ } else {
163
+ values = {
164
+ value: this.emptyValue,
165
+ };
166
+ }
167
+
168
+ super.declareData(
169
+ values,
170
+ {
171
+ refDate: undefined,
172
+ disabled: undefined,
173
+ minValue: undefined,
174
+ minExclusive: undefined,
175
+ maxValue: undefined,
176
+ maxExclusive: undefined,
177
+ },
178
+ ...args,
179
+ );
180
+ }
181
+
182
+ init(): void {
183
+ super.init();
184
+ }
185
+
186
+ prepareData(context: RenderingContext, instance: MonthPickerInstance): void {
187
+ let { data } = instance;
188
+ data.stateMods = {
189
+ disabled: data.disabled,
190
+ };
191
+
192
+ if (!this.range && data.value)
193
+ data.date = monthStart(parseDateInvariant(data.value));
194
+
195
+ if (this.range) {
196
+ if (data.from) data.from = monthStart(parseDateInvariant(data.from));
197
+
198
+ if (data.to) {
199
+ let date = parseDateInvariant(data.to);
200
+ if (this.inclusiveTo) date.setDate(date.getDate() + 1);
201
+ data.to = monthStart(date);
202
+ }
203
+ }
204
+
205
+ if (data.refDate)
206
+ data.refDate = monthStart(parseDateInvariant(data.refDate));
207
+
208
+ if (data.maxValue)
209
+ data.maxValue = monthStart(parseDateInvariant(data.maxValue));
210
+
211
+ if (data.minValue)
212
+ data.minValue = monthStart(parseDateInvariant(data.minValue));
213
+
214
+ if (this.onCreateIsMonthDateSelectable) {
215
+ instance.isMonthDateSelectable = instance.invoke(
216
+ "onCreateIsMonthDateSelectable",
217
+ data.validationParams,
218
+ instance,
219
+ );
220
+ }
221
+
222
+ super.prepareData(context, instance);
223
+ }
224
+
225
+ validate(context: RenderingContext, instance: MonthPickerInstance): void {
226
+ super.validate(context, instance);
227
+ let { data } = instance;
228
+ if (!data.error && data.date) {
229
+ let d;
230
+ if (data.maxValue) {
231
+ d = dateDiff(data.date, data.maxValue);
232
+ if (d > 0)
233
+ data.error = StringTemplate.format(
234
+ this.maxValueErrorText,
235
+ data.maxValue,
236
+ );
237
+ else if (d == 0 && data.maxExclusive)
238
+ data.error = StringTemplate.format(
239
+ this.maxExclusiveErrorText,
240
+ data.maxValue,
241
+ );
242
+ }
243
+
244
+ if (data.minValue) {
245
+ d = dateDiff(data.date, data.minValue);
246
+ if (d < 0)
247
+ data.error = StringTemplate.format(
248
+ this.minValueErrorText,
249
+ data.minValue,
250
+ );
251
+ else if (d == 0 && data.minExclusive)
252
+ data.error = StringTemplate.format(
253
+ this.minExclusiveErrorText,
254
+ data.minValue,
255
+ );
256
+ }
257
+ }
258
+ }
259
+
260
+ renderInput(
261
+ context: RenderingContext,
262
+ instance: MonthPickerInstance,
263
+ key: string,
264
+ ): React.ReactNode {
265
+ return (
266
+ <MonthPickerComponent
267
+ key={key}
268
+ instance={instance}
269
+ onBlur={this.onBlur}
270
+ onFocusOut={this.onFocusOut}
271
+ onKeyDown={this.onKeyDown}
272
+ autoFocus={this.autoFocus}
273
+ />
274
+ );
275
+ }
276
+
277
+ handleSelect(
278
+ e: React.KeyboardEvent | React.MouseEvent | React.TouchEvent,
279
+ instance: MonthPickerInstance,
280
+ date1: Date,
281
+ date2: Date,
282
+ ): void {
283
+ let { data, widget, isMonthDateSelectable } = instance;
284
+ let encode = widget.encoding || Culture.getDefaultDateEncoding();
285
+
286
+ if (data.disabled) return;
287
+
288
+ if (isMonthDateSelectable && !isMonthDateSelectable(date1)) return;
289
+ if (!dateSelectableCheck(date1, data)) return;
290
+
291
+ if (
292
+ this.onBeforeSelect &&
293
+ instance.invoke("onBeforeSelect", e, instance, date1, date2) === false
294
+ )
295
+ return;
296
+
297
+ if (this.range) {
298
+ instance.set("from", encode(date1));
299
+ let toDate = new Date(date2);
300
+ if (this.inclusiveTo) toDate.setDate(toDate.getDate() - 1);
301
+ instance.set("to", encode(toDate));
302
+ } else instance.set("value", encode(date1));
303
+
304
+ if (this.onSelect) instance.invoke("onSelect", instance, date1, date2);
305
+ }
306
+ }
307
+
308
+ MonthPicker.prototype.baseClass = "monthpicker";
309
+ MonthPicker.prototype.range = false;
310
+ MonthPicker.prototype.startYear = 1980;
311
+ MonthPicker.prototype.endYear = 2050;
312
+ MonthPicker.prototype.bufferSize = 15;
313
+ MonthPicker.prototype.hideQuarters = false;
314
+
315
+ // Localization
316
+ MonthPicker.prototype.maxValueErrorText = "Select {0:d} or before.";
317
+ MonthPicker.prototype.maxExclusiveErrorText = "Select a date before {0:d}.";
318
+ MonthPicker.prototype.minValueErrorText = "Select {0:d} or later.";
319
+ MonthPicker.prototype.minExclusiveErrorText = "Select a date after {0:d}.";
320
+ MonthPicker.prototype.inclusiveTo = false;
321
+
322
+ Localization.registerPrototype("cx/widgets/MonthPicker", MonthPicker);
323
+
324
+ Widget.alias("month-picker", MonthPicker);
325
+
326
+ const dateSelectableCheck = (
327
+ date: Date,
328
+ data: Record<string, any>,
329
+ ): boolean => {
330
+ if (
331
+ data.maxValue &&
332
+ !upperBoundCheck(date, data.maxValue as Date, data.maxExclusive)
333
+ )
334
+ return false;
335
+
336
+ if (
337
+ data.minValue &&
338
+ !lowerBoundCheck(date, data.minValue as Date, data.minExclusive)
339
+ )
340
+ return false;
341
+
342
+ return true;
343
+ };
344
+
345
+ const monthNumber = (date: Date): number => {
346
+ return date.getFullYear() * 12 + date.getMonth();
347
+ };
348
+
349
+ interface MonthPickerComponentProps {
350
+ instance: MonthPickerInstance;
351
+ onBlur?:
352
+ | string
353
+ | ((e: React.FocusEvent, instance: MonthPickerInstance) => void);
354
+ onFocusOut?:
355
+ | string
356
+ | ((e: React.FocusEvent, instance: MonthPickerInstance) => void);
357
+ onKeyDown?:
358
+ | string
359
+ | ((
360
+ e: React.KeyboardEvent,
361
+ instance: MonthPickerInstance,
362
+ ) => boolean | void);
363
+ autoFocus?: boolean;
364
+ }
365
+
366
+ interface MonthPickerComponentState {
367
+ cursorYear: number;
368
+ cursorMonth: number;
369
+ cursorQuarter: number;
370
+ column: string;
371
+ start: number;
372
+ end: number;
373
+ state?: string;
374
+ focused?: boolean;
375
+ yearHeight?: number;
376
+ }
377
+
378
+ interface CursorInfo {
379
+ column: string;
380
+ cursorYear: number;
381
+ cursorMonth: number;
382
+ cursorQuarter: number;
383
+ }
384
+
385
+ export class MonthPickerComponent extends VDOM.Component<
386
+ MonthPickerComponentProps,
387
+ MonthPickerComponentState
388
+ > {
389
+ dom: {
390
+ el?: HTMLDivElement | null;
391
+ table?: HTMLTableElement | null;
392
+ } = {};
393
+ dragStartDates?: [Date, Date];
394
+
395
+ constructor(props: MonthPickerComponentProps) {
396
+ super(props);
397
+ let { data, widget } = props.instance;
398
+
399
+ let cursor = monthStart(
400
+ data.refDate ? data.refDate : data.date || data.from || new Date(),
401
+ );
402
+
403
+ this.dom = {};
404
+
405
+ this.state = {
406
+ cursorYear: cursor.getFullYear(),
407
+ cursorMonth: cursor.getMonth() + 1,
408
+ cursorQuarter: cursor.getMonth() / 3,
409
+ column: "M",
410
+ start: widget.startYear,
411
+ end: Math.min(widget.startYear + widget.bufferSize, widget.endYear),
412
+ };
413
+
414
+ this.handleMouseDown = this.handleMouseDown.bind(this);
415
+ this.handleMouseUp = this.handleMouseUp.bind(this);
416
+ this.handleMouseEnter = this.handleMouseEnter.bind(this);
417
+ this.handleKeyPress = this.handleKeyPress.bind(this);
418
+ this.handleTouchMove = this.handleTouchMove.bind(this);
419
+ this.handleTouchEnd = this.handleTouchEnd.bind(this);
420
+ }
421
+
422
+ extractCursorInfo(el: HTMLElement): CursorInfo | false {
423
+ const dataPoint = el.getAttribute("data-point");
424
+ if (!dataPoint) return false;
425
+ let parts = dataPoint.split("-");
426
+ if (parts[0] != "Y") return false;
427
+ let cursor: CursorInfo = {
428
+ column: "Y",
429
+ cursorYear: Number(parts[1]),
430
+ cursorMonth: 1,
431
+ cursorQuarter: 1,
432
+ };
433
+ if (parts.length == 4) {
434
+ cursor.column = parts[2];
435
+ if (cursor.column == "M") cursor.cursorMonth = Number(parts[3]);
436
+ else cursor.cursorQuarter = Number(parts[3]);
437
+ }
438
+ return cursor;
439
+ }
440
+
441
+ moveCursor<K extends keyof MonthPickerComponentState>(
442
+ e: React.KeyboardEvent | React.MouseEvent | React.TouchEvent,
443
+ data: Pick<MonthPickerComponentState, K>,
444
+ options: { ensureVisible?: boolean; select?: boolean } = {},
445
+ ): void {
446
+ //e.preventDefault();
447
+ e.stopPropagation();
448
+
449
+ if ("cursorYear" in data && data.cursorYear !== undefined) {
450
+ let { startYear, endYear } = this.props.instance.widget;
451
+ (data as any).cursorYear = Math.max(
452
+ startYear,
453
+ Math.min(endYear, data.cursorYear as number),
454
+ );
455
+ }
456
+
457
+ if (Object.keys(data).every((k) => this.state[k as K] == (data as any)[k]))
458
+ return;
459
+
460
+ this.setState(data, () => {
461
+ if (options.ensureVisible) {
462
+ let index = this.state.cursorYear - this.state.start;
463
+ let tbody = this.dom.table?.children?.[index];
464
+ if (tbody) scrollElementIntoView(tbody);
465
+ }
466
+
467
+ if (options.select) {
468
+ let { instance } = this.props;
469
+ let { widget, data } = instance;
470
+
471
+ let [cursorFromDate, cursorToDate] = this.getCursorDates();
472
+ let originFromDate = cursorFromDate,
473
+ originToDate = cursorToDate;
474
+
475
+ if (widget.range) {
476
+ if (data.from) originFromDate = data.from;
477
+ if (data.to) originToDate = data.to;
478
+ }
479
+
480
+ widget.handleSelect(
481
+ e,
482
+ instance,
483
+ minDate(originFromDate, cursorFromDate),
484
+ maxDate(originToDate, cursorToDate),
485
+ );
486
+ }
487
+ });
488
+ }
489
+
490
+ handleKeyPress(e: React.KeyboardEvent): void {
491
+ let { instance } = this.props;
492
+ let { widget } = this.props.instance;
493
+ let { cursorMonth, cursorYear, cursorQuarter, column } = this.state;
494
+ let select = widget.range && e.shiftKey;
495
+
496
+ switch (e.keyCode) {
497
+ case KeyCode.enter:
498
+ // if (widget.range && e.shiftKey && !this.dragStartDates) {
499
+ // this.handleMouseDown(e, {}, false);
500
+ // } else {
501
+ // this.handleMouseUp(e);
502
+ // }
503
+ this.handleMouseUp(e);
504
+ e.preventDefault();
505
+ e.stopPropagation();
506
+ break;
507
+
508
+ case KeyCode.left:
509
+ if (column == "Y")
510
+ this.moveCursor(
511
+ e,
512
+ {
513
+ cursorQuarter: 3,
514
+ cursorYear: cursorYear - 1,
515
+ column: "Q",
516
+ },
517
+ { select },
518
+ );
519
+ else if (column == "Q")
520
+ this.moveCursor(
521
+ e,
522
+ { cursorMonth: cursorQuarter * 4, column: "M" },
523
+ { select },
524
+ );
525
+ else if (column == "M" && (cursorMonth - 1) % 3 == 0)
526
+ this.moveCursor(e, { column: "Y" });
527
+ else this.moveCursor(e, { cursorMonth: cursorMonth - 1 }, { select });
528
+ break;
529
+
530
+ case KeyCode.right:
531
+ if (column == "Y")
532
+ this.moveCursor(e, { cursorMonth: 1, column: "M" }, { select });
533
+ else if (column == "Q")
534
+ this.moveCursor(
535
+ e,
536
+ {
537
+ column: "Y",
538
+ cursorYear: cursorQuarter == 3 ? cursorYear + 1 : cursorYear,
539
+ },
540
+ { select },
541
+ );
542
+ else if (column == "M" && (cursorMonth - 1) % 3 == 2)
543
+ this.moveCursor(
544
+ e,
545
+ {
546
+ column: "Q",
547
+ cursorQuarter: Math.floor((cursorMonth - 1) / 3),
548
+ },
549
+ { select },
550
+ );
551
+ else this.moveCursor(e, { cursorMonth: cursorMonth + 1 }, { select });
552
+ break;
553
+
554
+ case KeyCode.up:
555
+ e.preventDefault(); // prevent scroll
556
+ if (column == "Y")
557
+ this.moveCursor(
558
+ e,
559
+ { cursorYear: cursorYear - 1 },
560
+ { ensureVisible: true, select },
561
+ );
562
+ else if (column == "Q")
563
+ this.moveCursor(
564
+ e,
565
+ {
566
+ cursorQuarter: (cursorQuarter + 3) % 4,
567
+ cursorYear: cursorQuarter == 0 ? cursorYear - 1 : cursorYear,
568
+ },
569
+ { ensureVisible: true, select },
570
+ );
571
+ else if (column == "M")
572
+ if (cursorMonth > 3)
573
+ this.moveCursor(
574
+ e,
575
+ { cursorMonth: cursorMonth - 3 },
576
+ { ensureVisible: true, select },
577
+ );
578
+ else
579
+ this.moveCursor(
580
+ e,
581
+ { cursorMonth: cursorMonth + 9, cursorYear: cursorYear - 1 },
582
+ { ensureVisible: true, select },
583
+ );
584
+ break;
585
+
586
+ case KeyCode.down:
587
+ e.preventDefault(); // prevent scroll
588
+ if (column == "Y")
589
+ this.moveCursor(
590
+ e,
591
+ { cursorYear: cursorYear + 1 },
592
+ { ensureVisible: true, select },
593
+ );
594
+ else if (column == "Q")
595
+ this.moveCursor(
596
+ e,
597
+ {
598
+ cursorQuarter: (cursorQuarter + 1) % 4,
599
+ cursorYear: cursorQuarter == 3 ? cursorYear + 1 : cursorYear,
600
+ },
601
+ { ensureVisible: true, select },
602
+ );
603
+ else if (column == "M")
604
+ if (cursorMonth < 10)
605
+ this.moveCursor(
606
+ e,
607
+ { cursorMonth: cursorMonth + 3 },
608
+ { ensureVisible: true, select },
609
+ );
610
+ else
611
+ this.moveCursor(
612
+ e,
613
+ { cursorMonth: cursorMonth - 9, cursorYear: cursorYear + 1 },
614
+ { ensureVisible: true, select },
615
+ );
616
+ break;
617
+
618
+ case KeyCode.pageUp:
619
+ this.moveCursor(
620
+ e,
621
+ { cursorYear: this.state.cursorYear - 1 },
622
+ { ensureVisible: true, select },
623
+ );
624
+ break;
625
+
626
+ case KeyCode.pageDown:
627
+ this.moveCursor(
628
+ e,
629
+ { cursorYear: this.state.cursorYear + 1 },
630
+ { ensureVisible: true, select },
631
+ );
632
+ break;
633
+
634
+ default:
635
+ if (widget.onKeyDown) instance.invoke("onKeyDown", e, instance);
636
+ break;
637
+ }
638
+ }
639
+
640
+ handleBlur(e: React.FocusEvent): void {
641
+ FocusManager.nudge();
642
+ let { instance } = this.props;
643
+ let { widget } = instance;
644
+ if (widget.onBlur) instance.invoke("onBlur", e, instance);
645
+ this.setState({
646
+ focused: false,
647
+ });
648
+ }
649
+
650
+ handleFocus(e: React.FocusEvent): void {
651
+ this.setState({
652
+ focused: true,
653
+ });
654
+ if (this.props.onFocusOut && this.dom.el)
655
+ oneFocusOut(this, this.dom.el, this.handleFocusOut.bind(this));
656
+ }
657
+
658
+ handleFocusOut(e: React.FocusEvent): void {
659
+ let { instance } = this.props;
660
+ let { widget } = instance;
661
+ if (widget.onFocusOut) instance.invoke("onFocusOut", e, instance);
662
+ }
663
+
664
+ getCursorDates(cursor?: CursorInfo): [Date, Date] {
665
+ let { cursorMonth, cursorYear, cursorQuarter, column } =
666
+ cursor ?? this.state;
667
+ switch (column) {
668
+ case "M":
669
+ return [
670
+ new Date(cursorYear, cursorMonth - 1, 1),
671
+ new Date(cursorYear, cursorMonth, 1),
672
+ ];
673
+
674
+ case "Q":
675
+ return [
676
+ new Date(cursorYear, cursorQuarter * 3, 1),
677
+ new Date(cursorYear, cursorQuarter * 3 + 3, 1),
678
+ ];
679
+
680
+ default:
681
+ case "Y":
682
+ return [new Date(cursorYear, 0, 1), new Date(cursorYear + 1, 0, 1)];
683
+ }
684
+ }
685
+
686
+ handleTouchMove(e: React.TouchEvent): void {
687
+ let cursorPos = getCursorPos(e);
688
+ let el = document.elementFromPoint(cursorPos.clientX, cursorPos.clientY);
689
+ if (
690
+ this.dom.table &&
691
+ el &&
692
+ this.dom.table.contains(el) &&
693
+ el instanceof HTMLElement &&
694
+ isString(el.dataset.point)
695
+ ) {
696
+ let cursor = this.extractCursorInfo(el);
697
+ if (cursor) this.moveCursor(e, cursor);
698
+ }
699
+ }
700
+
701
+ handleTouchEnd(e: React.TouchEvent): void {
702
+ if (this.state.state == "drag") this.handleMouseUp(e);
703
+ }
704
+
705
+ handleMouseEnter(e: React.MouseEvent): void {
706
+ if (this.state.state != "drag") return;
707
+ let cursor = this.extractCursorInfo(e.target as HTMLElement);
708
+ if (cursor) {
709
+ this.moveCursor(e, cursor);
710
+ }
711
+ }
712
+
713
+ handleMouseDown(
714
+ e: React.MouseEvent | React.TouchEvent,
715
+ cursor?: CursorInfo | false,
716
+ drag: boolean = true,
717
+ ): void {
718
+ let { instance } = this.props;
719
+ let { widget } = instance;
720
+
721
+ if (!cursor) {
722
+ cursor = this.extractCursorInfo(e.currentTarget as HTMLElement);
723
+ if (!cursor) return;
724
+ this.moveCursor(e, cursor, { select: !widget.range });
725
+ }
726
+
727
+ e.stopPropagation();
728
+ preventFocusOnTouch(e);
729
+
730
+ this.dragStartDates = this.getCursorDates(cursor);
731
+ if (drag) {
732
+ this.setState({
733
+ state: "drag",
734
+ ...cursor,
735
+ });
736
+ }
737
+ }
738
+
739
+ handleMouseUp(
740
+ e: React.KeyboardEvent | React.MouseEvent | React.TouchEvent,
741
+ ): void {
742
+ let { instance } = this.props;
743
+ let { widget, data } = instance;
744
+
745
+ e.stopPropagation();
746
+ e.preventDefault();
747
+
748
+ let [cursorFromDate, cursorToDate] = this.getCursorDates();
749
+ let originFromDate = cursorFromDate,
750
+ originToDate = cursorToDate;
751
+ if (widget.range && e.shiftKey) {
752
+ if (data.from) originFromDate = data.from;
753
+ if (data.to) originToDate = data.to;
754
+ } else if (this.state.state == "drag") {
755
+ if (widget.range) {
756
+ [originFromDate, originToDate] = this.dragStartDates!;
757
+ }
758
+ this.setState({ state: "normal" });
759
+ } else {
760
+ //skip mouse events originated somewhere else
761
+ if (e.type != "keydown") return;
762
+ }
763
+ widget.handleSelect(
764
+ e,
765
+ instance,
766
+ minDate(originFromDate, cursorFromDate),
767
+ maxDate(originToDate, cursorToDate),
768
+ );
769
+ }
770
+
771
+ render(): React.ReactNode {
772
+ let { instance } = this.props;
773
+ let { data, widget, isMonthDateSelectable } = instance;
774
+ let { CSS, baseClass, startYear, endYear, hideQuarters } = widget;
775
+
776
+ let years = [];
777
+
778
+ let { start, end } = this.state;
779
+
780
+ let from = 10000,
781
+ to = 0,
782
+ a,
783
+ b;
784
+
785
+ if (data.date && !widget.range) {
786
+ from = monthNumber(data.date);
787
+ to = from + 0.1;
788
+ } else if (widget.range) {
789
+ if (this.state.state == "drag") {
790
+ let [originFromDate, originToDate] = this.dragStartDates!;
791
+ let [cursorFromDate, cursorToDate] = this.getCursorDates();
792
+ a = Math.min(monthNumber(originFromDate), monthNumber(cursorFromDate));
793
+ b = Math.max(monthNumber(originToDate), monthNumber(cursorToDate));
794
+ from = Math.min(a, b);
795
+ to = Math.max(a, b);
796
+ } else if (data.from && data.to) {
797
+ a = monthNumber(data.from);
798
+ b = monthNumber(data.to);
799
+ from = Math.min(a, b);
800
+ to = Math.max(a, b);
801
+ }
802
+ }
803
+
804
+ let monthNames = Culture.getDateTimeCulture().getMonthNames("short");
805
+ let showCursor = this.state.focused;
806
+
807
+ for (let y = start; y <= end; y++) {
808
+ let selectableMonths = 0b111111111111;
809
+ // Loop through the months in a year to check if all months are unselectable
810
+ for (let i = 0; i < 12; i++) {
811
+ if (
812
+ (isMonthDateSelectable &&
813
+ !isMonthDateSelectable(new Date(y, i, 1))) ||
814
+ !dateSelectableCheck(new Date(y, i, 1), data)
815
+ ) {
816
+ // Set month as unselectable at specified bit
817
+ selectableMonths &= ~(1 << i);
818
+ }
819
+ }
820
+
821
+ // All bits are 0 - all months are unselectable
822
+ const unselectableYear = selectableMonths === 0;
823
+
824
+ let rows = [];
825
+ for (let q = 0; q < 4; q++) {
826
+ let row = [];
827
+ if (q == 0) {
828
+ row.push(
829
+ <th
830
+ key="year"
831
+ rowSpan={4}
832
+ data-point={`Y-${y}`}
833
+ className={CSS.expand(
834
+ CSS.element(baseClass, "year", {
835
+ cursor:
836
+ showCursor &&
837
+ this.state.column == "Y" &&
838
+ y == this.state.cursorYear,
839
+ }),
840
+ CSS.state({ unselectable: unselectableYear }),
841
+ )}
842
+ onMouseEnter={
843
+ unselectableYear ? undefined : this.handleMouseEnter
844
+ }
845
+ onMouseDown={unselectableYear ? undefined : this.handleMouseDown}
846
+ onMouseUp={unselectableYear ? undefined : this.handleMouseUp}
847
+ >
848
+ {y}
849
+ </th>,
850
+ );
851
+ }
852
+
853
+ for (let i = 0; i < 3; i++) {
854
+ let m = q * 3 + i + 1;
855
+ const unselectableMonth = (selectableMonths & (1 << (m - 1))) === 0;
856
+ let mno = y * 12 + m - 1;
857
+ let handle = true; //isTouchDevice(); //mno === from || mno === to - 1;
858
+ row.push(
859
+ <td
860
+ key={`M${m}`}
861
+ className={CSS.state({
862
+ cursor:
863
+ showCursor &&
864
+ this.state.column == "M" &&
865
+ y == this.state.cursorYear &&
866
+ m == this.state.cursorMonth,
867
+ handle,
868
+ selected: mno >= from && mno < to,
869
+ unselectable: unselectableMonth,
870
+ })}
871
+ data-point={`Y-${y}-M-${m}`}
872
+ onMouseEnter={
873
+ unselectableMonth ? undefined : this.handleMouseEnter
874
+ }
875
+ onMouseDown={unselectableMonth ? undefined : this.handleMouseDown}
876
+ onMouseUp={unselectableMonth ? undefined : this.handleMouseUp}
877
+ onTouchStart={
878
+ unselectableMonth ? undefined : this.handleMouseDown
879
+ }
880
+ onTouchMove={unselectableMonth ? undefined : this.handleTouchMove}
881
+ onTouchEnd={this.handleMouseUp}
882
+ >
883
+ {monthNames[m - 1].substr(0, 3)}
884
+ </td>,
885
+ );
886
+ }
887
+
888
+ if (!hideQuarters) {
889
+ let unselectableQuarter = true;
890
+ const start = q * 3;
891
+ for (let i = start; i < start + 3; i++) {
892
+ if ((selectableMonths & (1 << i)) !== 0) {
893
+ // found a selectable month in a quarter
894
+ unselectableQuarter = false;
895
+ break;
896
+ }
897
+ }
898
+
899
+ row.push(
900
+ <th
901
+ key={`q${q}`}
902
+ className={CSS.state({
903
+ cursor:
904
+ showCursor &&
905
+ this.state.column == "Q" &&
906
+ y == this.state.cursorYear &&
907
+ q == this.state.cursorQuarter,
908
+ unselectable: unselectableQuarter,
909
+ })}
910
+ data-point={`Y-${y}-Q-${q}`}
911
+ onMouseEnter={
912
+ unselectableQuarter ? undefined : this.handleMouseEnter
913
+ }
914
+ onMouseDown={
915
+ unselectableQuarter ? undefined : this.handleMouseDown
916
+ }
917
+ onMouseUp={unselectableQuarter ? undefined : this.handleMouseUp}
918
+ >
919
+ {`Q${q + 1}`}
920
+ </th>,
921
+ );
922
+ }
923
+
924
+ rows.push(row);
925
+ }
926
+ years.push(rows);
927
+ }
928
+
929
+ return (
930
+ <div
931
+ ref={(el) => {
932
+ this.dom.el = el;
933
+ }}
934
+ className={data.classNames}
935
+ style={data.style}
936
+ tabIndex={data.disabled ? null : data.tabIndex || 0}
937
+ onKeyDown={this.handleKeyPress}
938
+ onMouseDown={stopPropagation}
939
+ onMouseMove={(e) =>
940
+ tooltipMouseMove(e, ...getFieldTooltip(this.props.instance))
941
+ }
942
+ onMouseLeave={this.handleMouseLeave.bind(this)}
943
+ onFocus={(e) => this.handleFocus(e)}
944
+ onBlur={this.handleBlur.bind(this)}
945
+ onScroll={this.onScroll.bind(this)}
946
+ >
947
+ {this.state.yearHeight && (
948
+ <div
949
+ style={{
950
+ height: `${(start - startYear) * this.state.yearHeight}px`,
951
+ }}
952
+ />
953
+ )}
954
+ <table
955
+ ref={(el) => {
956
+ this.dom.table = el;
957
+ }}
958
+ >
959
+ {years.map((rows, y) => (
960
+ <tbody key={start + y}>
961
+ {rows.map((cells, i) => (
962
+ <tr key={i}>{cells}</tr>
963
+ ))}
964
+ </tbody>
965
+ ))}
966
+ </table>
967
+ {this.state.yearHeight && (
968
+ <div
969
+ style={{
970
+ height: `${Math.max(0, endYear - end) * this.state.yearHeight}px`,
971
+ }}
972
+ />
973
+ )}
974
+ </div>
975
+ );
976
+ }
977
+
978
+ onScroll(): void {
979
+ if (!this.dom.el || !this.state.yearHeight) return;
980
+ let { startYear, endYear, bufferSize } = this.props.instance.widget;
981
+ let visibleItems = ceil5(
982
+ Math.ceil(this.dom.el.offsetHeight / this.state.yearHeight),
983
+ );
984
+ let start = Math.max(
985
+ startYear,
986
+ startYear +
987
+ floor5(Math.floor(this.dom.el.scrollTop / this.state.yearHeight)) -
988
+ visibleItems,
989
+ );
990
+ if (start != this.state.start) {
991
+ this.setState({
992
+ start,
993
+ end: Math.min(start + bufferSize, endYear),
994
+ });
995
+ }
996
+ }
997
+
998
+ handleMouseLeave(e: React.MouseEvent): void {
999
+ tooltipMouseLeave(e, ...getFieldTooltip(this.props.instance));
1000
+ }
1001
+
1002
+ componentDidMount(): void {
1003
+ //non-input, ok to focus on mobile
1004
+ if (this.props.autoFocus && this.dom.el) this.dom.el.focus();
1005
+
1006
+ if (this.dom.el && this.dom.table) {
1007
+ tooltipParentDidMount(
1008
+ this.dom.el,
1009
+ ...getFieldTooltip(this.props.instance),
1010
+ );
1011
+ let yearHeight =
1012
+ this.dom.table.scrollHeight /
1013
+ (this.props.instance.widget.bufferSize + 1);
1014
+ this.setState(
1015
+ {
1016
+ yearHeight: yearHeight,
1017
+ },
1018
+ () => {
1019
+ let { widget, data } = this.props.instance;
1020
+ let { startYear } = widget;
1021
+ let yearCount = 1;
1022
+ if (widget.range && data.from && data.to) {
1023
+ yearCount = data.to.getFullYear() - data.from.getFullYear() + 1;
1024
+ if (data.to.getMonth() == 0 && data.to.getDate() == 1) yearCount--;
1025
+ }
1026
+ if (this.dom.el && this.state.yearHeight) {
1027
+ this.dom.el.scrollTop =
1028
+ (this.state.cursorYear - startYear + yearCount / 2) *
1029
+ this.state.yearHeight -
1030
+ this.dom.el.offsetHeight / 2;
1031
+ }
1032
+ },
1033
+ );
1034
+ }
1035
+ }
1036
+
1037
+ UNSAFE_componentWillReceiveProps(props: MonthPickerComponentProps): void {
1038
+ this.setState({
1039
+ state: "normal",
1040
+ });
1041
+ if (this.dom.el) {
1042
+ tooltipParentWillReceiveProps(
1043
+ this.dom.el,
1044
+ ...getFieldTooltip(props.instance),
1045
+ );
1046
+ }
1047
+ }
1048
+
1049
+ componentWillUnmount(): void {
1050
+ offFocusOut(this);
1051
+ tooltipParentWillUnmount(this.props.instance);
1052
+ }
1053
+ }
1054
+
1055
+ function ceil5(x: number): number {
1056
+ return Math.ceil(x / 5) * 5;
1057
+ }
1058
+
1059
+ function floor5(x: number): number {
1060
+ return Math.floor(x / 5) * 5;
1061
+ }