rn-grid-date-time-picker 1.0.0 → 1.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (384) hide show
  1. package/CHANGELOG.md +86 -0
  2. package/README.md +100 -638
  3. package/lib/commonjs/adapters/utcMidnight.js +15 -6
  4. package/lib/commonjs/adapters/utcMidnight.js.map +1 -1
  5. package/lib/commonjs/components/DateTimePicker.js +484 -85
  6. package/lib/commonjs/components/DateTimePicker.js.map +1 -1
  7. package/lib/commonjs/components/calendar/Calendar.js +21 -7
  8. package/lib/commonjs/components/calendar/Calendar.js.map +1 -1
  9. package/lib/commonjs/components/calendar/CalendarHeader.js +46 -6
  10. package/lib/commonjs/components/calendar/CalendarHeader.js.map +1 -1
  11. package/lib/commonjs/components/calendar/CalendarSwitch.js +150 -0
  12. package/lib/commonjs/components/calendar/CalendarSwitch.js.map +1 -0
  13. package/lib/commonjs/components/calendar/DayCell.js +8 -3
  14. package/lib/commonjs/components/calendar/DayCell.js.map +1 -1
  15. package/lib/commonjs/components/calendar/DayGrid.js +13 -2
  16. package/lib/commonjs/components/calendar/DayGrid.js.map +1 -1
  17. package/lib/commonjs/components/calendar/PeriodGrid.js +3 -1
  18. package/lib/commonjs/components/calendar/PeriodGrid.js.map +1 -1
  19. package/lib/commonjs/components/calendar/calendarStyles.js +3 -2
  20. package/lib/commonjs/components/calendar/calendarStyles.js.map +1 -1
  21. package/lib/commonjs/components/calendar/localeNames.js +46 -24
  22. package/lib/commonjs/components/calendar/localeNames.js.map +1 -1
  23. package/lib/commonjs/components/calendar/selection.js +9 -1
  24. package/lib/commonjs/components/calendar/selection.js.map +1 -1
  25. package/lib/commonjs/components/container/DefaultSheet.js +42 -11
  26. package/lib/commonjs/components/container/DefaultSheet.js.map +1 -1
  27. package/lib/commonjs/components/container/PickerContainer.js +4 -0
  28. package/lib/commonjs/components/container/PickerContainer.js.map +1 -1
  29. package/lib/commonjs/components/container/PickerLayout.js +1 -1
  30. package/lib/commonjs/components/container/PickerLayout.js.map +1 -1
  31. package/lib/commonjs/components/container/containerStyles.js +12 -4
  32. package/lib/commonjs/components/container/containerStyles.js.map +1 -1
  33. package/lib/commonjs/components/field/RangeFields.js +5 -1
  34. package/lib/commonjs/components/field/RangeFields.js.map +1 -1
  35. package/lib/commonjs/components/field/SegmentedInput.js +8 -3
  36. package/lib/commonjs/components/field/SegmentedInput.js.map +1 -1
  37. package/lib/commonjs/components/field/ValueField.js +64 -8
  38. package/lib/commonjs/components/field/ValueField.js.map +1 -1
  39. package/lib/commonjs/components/field/fieldStyles.js +17 -0
  40. package/lib/commonjs/components/field/fieldStyles.js.map +1 -1
  41. package/lib/commonjs/components/field/fieldText.js +29 -13
  42. package/lib/commonjs/components/field/fieldText.js.map +1 -1
  43. package/lib/commonjs/components/hooks/useBottomInset.js +32 -0
  44. package/lib/commonjs/components/hooks/useBottomInset.js.map +1 -0
  45. package/lib/commonjs/components/hooks/useKeyboardFold.js +36 -13
  46. package/lib/commonjs/components/hooks/useKeyboardFold.js.map +1 -1
  47. package/lib/commonjs/components/hooks/useOpenPicker.js +39 -17
  48. package/lib/commonjs/components/hooks/useOpenPicker.js.map +1 -1
  49. package/lib/commonjs/components/hooks/useTypedSegments.js +6 -2
  50. package/lib/commonjs/components/hooks/useTypedSegments.js.map +1 -1
  51. package/lib/commonjs/components/theme/defaultStrings.js +13 -3
  52. package/lib/commonjs/components/theme/defaultStrings.js.map +1 -1
  53. package/lib/commonjs/components/theme/direction.js +17 -0
  54. package/lib/commonjs/components/theme/direction.js.map +1 -0
  55. package/lib/commonjs/components/theme/numerals.js +16 -0
  56. package/lib/commonjs/components/theme/numerals.js.map +1 -0
  57. package/lib/commonjs/components/theme/styleKeys.js +10 -2
  58. package/lib/commonjs/components/theme/styleKeys.js.map +1 -1
  59. package/lib/commonjs/components/theme/tokens.js +4 -0
  60. package/lib/commonjs/components/theme/tokens.js.map +1 -1
  61. package/lib/commonjs/components/time/TimeRow.js +10 -2
  62. package/lib/commonjs/components/time/TimeRow.js.map +1 -1
  63. package/lib/commonjs/components/time/TypedTime.js +3 -1
  64. package/lib/commonjs/components/time/TypedTime.js.map +1 -1
  65. package/lib/commonjs/components/time/Wheel.js +20 -3
  66. package/lib/commonjs/components/time/Wheel.js.map +1 -1
  67. package/lib/commonjs/components/time/timeStyles.js +4 -5
  68. package/lib/commonjs/components/time/timeStyles.js.map +1 -1
  69. package/lib/commonjs/components/types.js +10 -0
  70. package/lib/commonjs/components/types.js.map +1 -1
  71. package/lib/commonjs/core/calendarSystem.js +123 -22
  72. package/lib/commonjs/core/calendarSystem.js.map +1 -1
  73. package/lib/commonjs/core/digits.js +25 -0
  74. package/lib/commonjs/core/digits.js.map +1 -0
  75. package/lib/commonjs/core/draft.js +31 -10
  76. package/lib/commonjs/core/draft.js.map +1 -1
  77. package/lib/commonjs/core/locale.js +20 -4
  78. package/lib/commonjs/core/locale.js.map +1 -1
  79. package/lib/commonjs/core/numerals.js +46 -0
  80. package/lib/commonjs/core/numerals.js.map +1 -0
  81. package/lib/commonjs/core/segmentEditor.js +9 -0
  82. package/lib/commonjs/core/segmentEditor.js.map +1 -1
  83. package/lib/commonjs/core/segments.js +2 -11
  84. package/lib/commonjs/core/segments.js.map +1 -1
  85. package/lib/commonjs/core/selectionRules.js +2 -1
  86. package/lib/commonjs/core/selectionRules.js.map +1 -1
  87. package/lib/commonjs/core/timeEntry.js +7 -91
  88. package/lib/commonjs/core/timeEntry.js.map +1 -1
  89. package/lib/commonjs/icons/index.js +5 -1
  90. package/lib/commonjs/icons/index.js.map +1 -1
  91. package/lib/commonjs/index.js +20 -0
  92. package/lib/commonjs/index.js.map +1 -1
  93. package/lib/module/adapters/utcMidnight.js +14 -5
  94. package/lib/module/adapters/utcMidnight.js.map +1 -1
  95. package/lib/module/components/DateTimePicker.js +490 -91
  96. package/lib/module/components/DateTimePicker.js.map +1 -1
  97. package/lib/module/components/calendar/Calendar.js +25 -11
  98. package/lib/module/components/calendar/Calendar.js.map +1 -1
  99. package/lib/module/components/calendar/CalendarHeader.js +47 -7
  100. package/lib/module/components/calendar/CalendarHeader.js.map +1 -1
  101. package/lib/module/components/calendar/CalendarSwitch.js +145 -0
  102. package/lib/module/components/calendar/CalendarSwitch.js.map +1 -0
  103. package/lib/module/components/calendar/DayCell.js +8 -3
  104. package/lib/module/components/calendar/DayCell.js.map +1 -1
  105. package/lib/module/components/calendar/DayGrid.js +14 -3
  106. package/lib/module/components/calendar/DayGrid.js.map +1 -1
  107. package/lib/module/components/calendar/PeriodGrid.js +3 -1
  108. package/lib/module/components/calendar/PeriodGrid.js.map +1 -1
  109. package/lib/module/components/calendar/calendarStyles.js +3 -2
  110. package/lib/module/components/calendar/calendarStyles.js.map +1 -1
  111. package/lib/module/components/calendar/localeNames.js +46 -24
  112. package/lib/module/components/calendar/localeNames.js.map +1 -1
  113. package/lib/module/components/calendar/selection.js +7 -0
  114. package/lib/module/components/calendar/selection.js.map +1 -1
  115. package/lib/module/components/container/DefaultSheet.js +42 -12
  116. package/lib/module/components/container/DefaultSheet.js.map +1 -1
  117. package/lib/module/components/container/PickerContainer.js +4 -0
  118. package/lib/module/components/container/PickerContainer.js.map +1 -1
  119. package/lib/module/components/container/PickerLayout.js +1 -1
  120. package/lib/module/components/container/PickerLayout.js.map +1 -1
  121. package/lib/module/components/container/containerStyles.js +11 -3
  122. package/lib/module/components/container/containerStyles.js.map +1 -1
  123. package/lib/module/components/field/RangeFields.js +5 -1
  124. package/lib/module/components/field/RangeFields.js.map +1 -1
  125. package/lib/module/components/field/SegmentedInput.js +8 -3
  126. package/lib/module/components/field/SegmentedInput.js.map +1 -1
  127. package/lib/module/components/field/ValueField.js +66 -10
  128. package/lib/module/components/field/ValueField.js.map +1 -1
  129. package/lib/module/components/field/fieldStyles.js +17 -0
  130. package/lib/module/components/field/fieldStyles.js.map +1 -1
  131. package/lib/module/components/field/fieldText.js +28 -11
  132. package/lib/module/components/field/fieldText.js.map +1 -1
  133. package/lib/module/components/hooks/useBottomInset.js +27 -0
  134. package/lib/module/components/hooks/useBottomInset.js.map +1 -0
  135. package/lib/module/components/hooks/useKeyboardFold.js +33 -12
  136. package/lib/module/components/hooks/useKeyboardFold.js.map +1 -1
  137. package/lib/module/components/hooks/useOpenPicker.js +39 -17
  138. package/lib/module/components/hooks/useOpenPicker.js.map +1 -1
  139. package/lib/module/components/hooks/useTypedSegments.js +6 -2
  140. package/lib/module/components/hooks/useTypedSegments.js.map +1 -1
  141. package/lib/module/components/theme/defaultStrings.js +11 -2
  142. package/lib/module/components/theme/defaultStrings.js.map +1 -1
  143. package/lib/module/components/theme/direction.js +13 -0
  144. package/lib/module/components/theme/direction.js.map +1 -0
  145. package/lib/module/components/theme/numerals.js +12 -0
  146. package/lib/module/components/theme/numerals.js.map +1 -0
  147. package/lib/module/components/theme/styleKeys.js +10 -2
  148. package/lib/module/components/theme/styleKeys.js.map +1 -1
  149. package/lib/module/components/theme/tokens.js +4 -0
  150. package/lib/module/components/theme/tokens.js.map +1 -1
  151. package/lib/module/components/time/TimeRow.js +11 -3
  152. package/lib/module/components/time/TimeRow.js.map +1 -1
  153. package/lib/module/components/time/TypedTime.js +3 -1
  154. package/lib/module/components/time/TypedTime.js.map +1 -1
  155. package/lib/module/components/time/Wheel.js +18 -2
  156. package/lib/module/components/time/Wheel.js.map +1 -1
  157. package/lib/module/components/time/timeStyles.js +4 -5
  158. package/lib/module/components/time/timeStyles.js.map +1 -1
  159. package/lib/module/components/types.js +10 -0
  160. package/lib/module/components/types.js.map +1 -1
  161. package/lib/module/core/calendarSystem.js +118 -19
  162. package/lib/module/core/calendarSystem.js.map +1 -1
  163. package/lib/module/core/digits.js +20 -0
  164. package/lib/module/core/digits.js.map +1 -0
  165. package/lib/module/core/draft.js +29 -9
  166. package/lib/module/core/draft.js.map +1 -1
  167. package/lib/module/core/locale.js +18 -3
  168. package/lib/module/core/locale.js.map +1 -1
  169. package/lib/module/core/numerals.js +39 -0
  170. package/lib/module/core/numerals.js.map +1 -0
  171. package/lib/module/core/segmentEditor.js +9 -0
  172. package/lib/module/core/segmentEditor.js.map +1 -1
  173. package/lib/module/core/segments.js +0 -8
  174. package/lib/module/core/segments.js.map +1 -1
  175. package/lib/module/core/selectionRules.js +2 -1
  176. package/lib/module/core/selectionRules.js.map +1 -1
  177. package/lib/module/core/timeEntry.js +7 -89
  178. package/lib/module/core/timeEntry.js.map +1 -1
  179. package/lib/module/icons/index.js +5 -1
  180. package/lib/module/icons/index.js.map +1 -1
  181. package/lib/module/index.js +2 -0
  182. package/lib/module/index.js.map +1 -1
  183. package/lib/typescript/commonjs/src/adapters/utcMidnight.d.ts.map +1 -1
  184. package/lib/typescript/commonjs/src/components/DateTimePicker.d.ts.map +1 -1
  185. package/lib/typescript/commonjs/src/components/calendar/Calendar.d.ts +8 -4
  186. package/lib/typescript/commonjs/src/components/calendar/Calendar.d.ts.map +1 -1
  187. package/lib/typescript/commonjs/src/components/calendar/CalendarHeader.d.ts.map +1 -1
  188. package/lib/typescript/commonjs/src/components/calendar/CalendarSwitch.d.ts +26 -0
  189. package/lib/typescript/commonjs/src/components/calendar/CalendarSwitch.d.ts.map +1 -0
  190. package/lib/typescript/commonjs/src/components/calendar/DayCell.d.ts +1 -1
  191. package/lib/typescript/commonjs/src/components/calendar/DayCell.d.ts.map +1 -1
  192. package/lib/typescript/commonjs/src/components/calendar/DayGrid.d.ts +2 -0
  193. package/lib/typescript/commonjs/src/components/calendar/DayGrid.d.ts.map +1 -1
  194. package/lib/typescript/commonjs/src/components/calendar/PeriodGrid.d.ts.map +1 -1
  195. package/lib/typescript/commonjs/src/components/calendar/calendarStyles.d.ts +1 -1
  196. package/lib/typescript/commonjs/src/components/calendar/calendarStyles.d.ts.map +1 -1
  197. package/lib/typescript/commonjs/src/components/calendar/localeNames.d.ts +8 -3
  198. package/lib/typescript/commonjs/src/components/calendar/localeNames.d.ts.map +1 -1
  199. package/lib/typescript/commonjs/src/components/calendar/selection.d.ts +2 -0
  200. package/lib/typescript/commonjs/src/components/calendar/selection.d.ts.map +1 -1
  201. package/lib/typescript/commonjs/src/components/container/DefaultSheet.d.ts +10 -2
  202. package/lib/typescript/commonjs/src/components/container/DefaultSheet.d.ts.map +1 -1
  203. package/lib/typescript/commonjs/src/components/container/PickerContainer.d.ts.map +1 -1
  204. package/lib/typescript/commonjs/src/components/container/containerStyles.d.ts +6 -0
  205. package/lib/typescript/commonjs/src/components/container/containerStyles.d.ts.map +1 -1
  206. package/lib/typescript/commonjs/src/components/field/RangeFields.d.ts +5 -0
  207. package/lib/typescript/commonjs/src/components/field/RangeFields.d.ts.map +1 -1
  208. package/lib/typescript/commonjs/src/components/field/SegmentedInput.d.ts.map +1 -1
  209. package/lib/typescript/commonjs/src/components/field/ValueField.d.ts +7 -0
  210. package/lib/typescript/commonjs/src/components/field/ValueField.d.ts.map +1 -1
  211. package/lib/typescript/commonjs/src/components/field/fieldStyles.d.ts +15 -0
  212. package/lib/typescript/commonjs/src/components/field/fieldStyles.d.ts.map +1 -1
  213. package/lib/typescript/commonjs/src/components/field/fieldText.d.ts +14 -5
  214. package/lib/typescript/commonjs/src/components/field/fieldText.d.ts.map +1 -1
  215. package/lib/typescript/commonjs/src/components/hooks/useBottomInset.d.ts +8 -0
  216. package/lib/typescript/commonjs/src/components/hooks/useBottomInset.d.ts.map +1 -0
  217. package/lib/typescript/commonjs/src/components/hooks/useKeyboardFold.d.ts +10 -2
  218. package/lib/typescript/commonjs/src/components/hooks/useKeyboardFold.d.ts.map +1 -1
  219. package/lib/typescript/commonjs/src/components/hooks/useOpenPicker.d.ts +2 -1
  220. package/lib/typescript/commonjs/src/components/hooks/useOpenPicker.d.ts.map +1 -1
  221. package/lib/typescript/commonjs/src/components/hooks/useTypedSegments.d.ts +3 -2
  222. package/lib/typescript/commonjs/src/components/hooks/useTypedSegments.d.ts.map +1 -1
  223. package/lib/typescript/commonjs/src/components/theme/defaultStrings.d.ts +13 -2
  224. package/lib/typescript/commonjs/src/components/theme/defaultStrings.d.ts.map +1 -1
  225. package/lib/typescript/commonjs/src/components/theme/direction.d.ts +8 -0
  226. package/lib/typescript/commonjs/src/components/theme/direction.d.ts.map +1 -0
  227. package/lib/typescript/commonjs/src/components/theme/numerals.d.ts +7 -0
  228. package/lib/typescript/commonjs/src/components/theme/numerals.d.ts.map +1 -0
  229. package/lib/typescript/commonjs/src/components/theme/styleKeys.d.ts +1 -1
  230. package/lib/typescript/commonjs/src/components/theme/styleKeys.d.ts.map +1 -1
  231. package/lib/typescript/commonjs/src/components/theme/tokens.d.ts +3 -1
  232. package/lib/typescript/commonjs/src/components/theme/tokens.d.ts.map +1 -1
  233. package/lib/typescript/commonjs/src/components/time/TimeRow.d.ts.map +1 -1
  234. package/lib/typescript/commonjs/src/components/time/TypedTime.d.ts.map +1 -1
  235. package/lib/typescript/commonjs/src/components/time/Wheel.d.ts +5 -0
  236. package/lib/typescript/commonjs/src/components/time/Wheel.d.ts.map +1 -1
  237. package/lib/typescript/commonjs/src/components/time/timeStyles.d.ts +0 -2
  238. package/lib/typescript/commonjs/src/components/time/timeStyles.d.ts.map +1 -1
  239. package/lib/typescript/commonjs/src/components/types.d.ts +84 -4
  240. package/lib/typescript/commonjs/src/components/types.d.ts.map +1 -1
  241. package/lib/typescript/commonjs/src/core/calendarSystem.d.ts +20 -5
  242. package/lib/typescript/commonjs/src/core/calendarSystem.d.ts.map +1 -1
  243. package/lib/typescript/commonjs/src/core/digits.d.ts +3 -0
  244. package/lib/typescript/commonjs/src/core/digits.d.ts.map +1 -0
  245. package/lib/typescript/commonjs/src/core/draft.d.ts +12 -1
  246. package/lib/typescript/commonjs/src/core/draft.d.ts.map +1 -1
  247. package/lib/typescript/commonjs/src/core/locale.d.ts +6 -0
  248. package/lib/typescript/commonjs/src/core/locale.d.ts.map +1 -1
  249. package/lib/typescript/commonjs/src/core/numerals.d.ts +15 -0
  250. package/lib/typescript/commonjs/src/core/numerals.d.ts.map +1 -0
  251. package/lib/typescript/commonjs/src/core/segmentEditor.d.ts.map +1 -1
  252. package/lib/typescript/commonjs/src/core/segments.d.ts +0 -2
  253. package/lib/typescript/commonjs/src/core/segments.d.ts.map +1 -1
  254. package/lib/typescript/commonjs/src/core/selectionRules.d.ts +3 -2
  255. package/lib/typescript/commonjs/src/core/selectionRules.d.ts.map +1 -1
  256. package/lib/typescript/commonjs/src/core/timeEntry.d.ts +4 -24
  257. package/lib/typescript/commonjs/src/core/timeEntry.d.ts.map +1 -1
  258. package/lib/typescript/commonjs/src/icons/index.d.ts.map +1 -1
  259. package/lib/typescript/commonjs/src/index.d.ts +7 -3
  260. package/lib/typescript/commonjs/src/index.d.ts.map +1 -1
  261. package/lib/typescript/module/src/adapters/utcMidnight.d.ts.map +1 -1
  262. package/lib/typescript/module/src/components/DateTimePicker.d.ts.map +1 -1
  263. package/lib/typescript/module/src/components/calendar/Calendar.d.ts +8 -4
  264. package/lib/typescript/module/src/components/calendar/Calendar.d.ts.map +1 -1
  265. package/lib/typescript/module/src/components/calendar/CalendarHeader.d.ts.map +1 -1
  266. package/lib/typescript/module/src/components/calendar/CalendarSwitch.d.ts +26 -0
  267. package/lib/typescript/module/src/components/calendar/CalendarSwitch.d.ts.map +1 -0
  268. package/lib/typescript/module/src/components/calendar/DayCell.d.ts +1 -1
  269. package/lib/typescript/module/src/components/calendar/DayCell.d.ts.map +1 -1
  270. package/lib/typescript/module/src/components/calendar/DayGrid.d.ts +2 -0
  271. package/lib/typescript/module/src/components/calendar/DayGrid.d.ts.map +1 -1
  272. package/lib/typescript/module/src/components/calendar/PeriodGrid.d.ts.map +1 -1
  273. package/lib/typescript/module/src/components/calendar/calendarStyles.d.ts +1 -1
  274. package/lib/typescript/module/src/components/calendar/calendarStyles.d.ts.map +1 -1
  275. package/lib/typescript/module/src/components/calendar/localeNames.d.ts +8 -3
  276. package/lib/typescript/module/src/components/calendar/localeNames.d.ts.map +1 -1
  277. package/lib/typescript/module/src/components/calendar/selection.d.ts +2 -0
  278. package/lib/typescript/module/src/components/calendar/selection.d.ts.map +1 -1
  279. package/lib/typescript/module/src/components/container/DefaultSheet.d.ts +10 -2
  280. package/lib/typescript/module/src/components/container/DefaultSheet.d.ts.map +1 -1
  281. package/lib/typescript/module/src/components/container/PickerContainer.d.ts.map +1 -1
  282. package/lib/typescript/module/src/components/container/containerStyles.d.ts +6 -0
  283. package/lib/typescript/module/src/components/container/containerStyles.d.ts.map +1 -1
  284. package/lib/typescript/module/src/components/field/RangeFields.d.ts +5 -0
  285. package/lib/typescript/module/src/components/field/RangeFields.d.ts.map +1 -1
  286. package/lib/typescript/module/src/components/field/SegmentedInput.d.ts.map +1 -1
  287. package/lib/typescript/module/src/components/field/ValueField.d.ts +7 -0
  288. package/lib/typescript/module/src/components/field/ValueField.d.ts.map +1 -1
  289. package/lib/typescript/module/src/components/field/fieldStyles.d.ts +15 -0
  290. package/lib/typescript/module/src/components/field/fieldStyles.d.ts.map +1 -1
  291. package/lib/typescript/module/src/components/field/fieldText.d.ts +14 -5
  292. package/lib/typescript/module/src/components/field/fieldText.d.ts.map +1 -1
  293. package/lib/typescript/module/src/components/hooks/useBottomInset.d.ts +8 -0
  294. package/lib/typescript/module/src/components/hooks/useBottomInset.d.ts.map +1 -0
  295. package/lib/typescript/module/src/components/hooks/useKeyboardFold.d.ts +10 -2
  296. package/lib/typescript/module/src/components/hooks/useKeyboardFold.d.ts.map +1 -1
  297. package/lib/typescript/module/src/components/hooks/useOpenPicker.d.ts +2 -1
  298. package/lib/typescript/module/src/components/hooks/useOpenPicker.d.ts.map +1 -1
  299. package/lib/typescript/module/src/components/hooks/useTypedSegments.d.ts +3 -2
  300. package/lib/typescript/module/src/components/hooks/useTypedSegments.d.ts.map +1 -1
  301. package/lib/typescript/module/src/components/theme/defaultStrings.d.ts +13 -2
  302. package/lib/typescript/module/src/components/theme/defaultStrings.d.ts.map +1 -1
  303. package/lib/typescript/module/src/components/theme/direction.d.ts +8 -0
  304. package/lib/typescript/module/src/components/theme/direction.d.ts.map +1 -0
  305. package/lib/typescript/module/src/components/theme/numerals.d.ts +7 -0
  306. package/lib/typescript/module/src/components/theme/numerals.d.ts.map +1 -0
  307. package/lib/typescript/module/src/components/theme/styleKeys.d.ts +1 -1
  308. package/lib/typescript/module/src/components/theme/styleKeys.d.ts.map +1 -1
  309. package/lib/typescript/module/src/components/theme/tokens.d.ts +3 -1
  310. package/lib/typescript/module/src/components/theme/tokens.d.ts.map +1 -1
  311. package/lib/typescript/module/src/components/time/TimeRow.d.ts.map +1 -1
  312. package/lib/typescript/module/src/components/time/TypedTime.d.ts.map +1 -1
  313. package/lib/typescript/module/src/components/time/Wheel.d.ts +5 -0
  314. package/lib/typescript/module/src/components/time/Wheel.d.ts.map +1 -1
  315. package/lib/typescript/module/src/components/time/timeStyles.d.ts +0 -2
  316. package/lib/typescript/module/src/components/time/timeStyles.d.ts.map +1 -1
  317. package/lib/typescript/module/src/components/types.d.ts +84 -4
  318. package/lib/typescript/module/src/components/types.d.ts.map +1 -1
  319. package/lib/typescript/module/src/core/calendarSystem.d.ts +20 -5
  320. package/lib/typescript/module/src/core/calendarSystem.d.ts.map +1 -1
  321. package/lib/typescript/module/src/core/digits.d.ts +3 -0
  322. package/lib/typescript/module/src/core/digits.d.ts.map +1 -0
  323. package/lib/typescript/module/src/core/draft.d.ts +12 -1
  324. package/lib/typescript/module/src/core/draft.d.ts.map +1 -1
  325. package/lib/typescript/module/src/core/locale.d.ts +6 -0
  326. package/lib/typescript/module/src/core/locale.d.ts.map +1 -1
  327. package/lib/typescript/module/src/core/numerals.d.ts +15 -0
  328. package/lib/typescript/module/src/core/numerals.d.ts.map +1 -0
  329. package/lib/typescript/module/src/core/segmentEditor.d.ts.map +1 -1
  330. package/lib/typescript/module/src/core/segments.d.ts +0 -2
  331. package/lib/typescript/module/src/core/segments.d.ts.map +1 -1
  332. package/lib/typescript/module/src/core/selectionRules.d.ts +3 -2
  333. package/lib/typescript/module/src/core/selectionRules.d.ts.map +1 -1
  334. package/lib/typescript/module/src/core/timeEntry.d.ts +4 -24
  335. package/lib/typescript/module/src/core/timeEntry.d.ts.map +1 -1
  336. package/lib/typescript/module/src/icons/index.d.ts.map +1 -1
  337. package/lib/typescript/module/src/index.d.ts +7 -3
  338. package/lib/typescript/module/src/index.d.ts.map +1 -1
  339. package/package.json +22 -7
  340. package/src/adapters/utcMidnight.ts +14 -5
  341. package/src/components/DateTimePicker.tsx +607 -108
  342. package/src/components/calendar/Calendar.tsx +54 -16
  343. package/src/components/calendar/CalendarHeader.tsx +41 -9
  344. package/src/components/calendar/CalendarSwitch.tsx +170 -0
  345. package/src/components/calendar/DayCell.tsx +16 -4
  346. package/src/components/calendar/DayGrid.tsx +18 -3
  347. package/src/components/calendar/PeriodGrid.tsx +3 -1
  348. package/src/components/calendar/calendarStyles.ts +3 -2
  349. package/src/components/calendar/localeNames.ts +52 -22
  350. package/src/components/calendar/selection.ts +8 -0
  351. package/src/components/container/DefaultSheet.tsx +60 -14
  352. package/src/components/container/PickerContainer.tsx +4 -0
  353. package/src/components/container/PickerLayout.tsx +1 -1
  354. package/src/components/container/containerStyles.ts +12 -3
  355. package/src/components/field/RangeFields.tsx +12 -2
  356. package/src/components/field/SegmentedInput.tsx +8 -3
  357. package/src/components/field/ValueField.tsx +94 -18
  358. package/src/components/field/fieldStyles.ts +6 -0
  359. package/src/components/field/fieldText.ts +68 -15
  360. package/src/components/hooks/useBottomInset.ts +33 -0
  361. package/src/components/hooks/useKeyboardFold.ts +41 -13
  362. package/src/components/hooks/useOpenPicker.ts +41 -18
  363. package/src/components/hooks/useTypedSegments.ts +7 -3
  364. package/src/components/theme/defaultStrings.ts +29 -4
  365. package/src/components/theme/direction.ts +12 -0
  366. package/src/components/theme/numerals.ts +11 -0
  367. package/src/components/theme/styleKeys.ts +18 -1
  368. package/src/components/theme/tokens.ts +5 -1
  369. package/src/components/time/TimeRow.tsx +23 -3
  370. package/src/components/time/TypedTime.tsx +3 -1
  371. package/src/components/time/Wheel.tsx +19 -5
  372. package/src/components/time/timeStyles.ts +2 -3
  373. package/src/components/types.ts +86 -4
  374. package/src/core/calendarSystem.ts +145 -21
  375. package/src/core/digits.ts +21 -0
  376. package/src/core/draft.ts +41 -13
  377. package/src/core/locale.ts +20 -3
  378. package/src/core/numerals.ts +35 -0
  379. package/src/core/segmentEditor.ts +13 -0
  380. package/src/core/segments.ts +0 -8
  381. package/src/core/selectionRules.ts +3 -2
  382. package/src/core/timeEntry.ts +6 -96
  383. package/src/icons/index.tsx +3 -1
  384. package/src/index.tsx +12 -2
@@ -6,20 +6,33 @@ import React, {
6
6
  useState,
7
7
  } from 'react';
8
8
  import { DateTime } from 'luxon';
9
+ import { View } from 'react-native';
9
10
  import { resolveAdapter } from '../adapters';
10
- import { daysBetween, isSameDay } from '../core/calendarDate';
11
+ import {
12
+ compareCalendarDates,
13
+ daysBetween,
14
+ isSameDay,
15
+ } from '../core/calendarDate';
11
16
  import { createConstraints } from '../core/constraints';
12
17
  import {
18
+ clearDraft as emptied,
13
19
  createDraft,
14
20
  DEFAULT_TIME,
15
21
  draftToWire,
22
+ sameDayRangeOk,
16
23
  setTime as applyTime,
17
24
  validateDraft,
18
25
  type Draft,
19
26
  type DraftOptions,
20
27
  type DraftTarget,
21
28
  } from '../core/draft';
22
- import { resolveCalendarSystem } from '../core/calendarSystem';
29
+ import {
30
+ hijriDateText,
31
+ hijriPeriods,
32
+ isHijriSupported,
33
+ resolveCalendarSystem,
34
+ type CalendarSystem,
35
+ } from '../core/calendarSystem';
23
36
  import {
24
37
  defaultLocale,
25
38
  deriveDateFormat,
@@ -27,6 +40,7 @@ import {
27
40
  deriveTimeFormat,
28
41
  normalizeDateFormat,
29
42
  } from '../core/locale';
43
+ import { arabicDigits, latinDigits, localeDigits } from '../core/numerals';
30
44
  import { withSelectionRules } from '../core/selectionRules';
31
45
  import type { ParsedSegments } from '../core/segments';
32
46
  import { checkTime, type TimeEntryIssue } from '../core/timeEntry';
@@ -38,13 +52,18 @@ import type {
38
52
  ValueAdapter,
39
53
  } from '../core/types';
40
54
  import Calendar from './calendar/Calendar';
55
+ import CalendarSwitch from './calendar/CalendarSwitch';
41
56
  import { localeNames } from './calendar/localeNames';
42
- import type { CalendarSelection } from './calendar/selection';
57
+ import { RtlContext } from './theme/direction';
58
+ import { NumeralsContext } from './theme/numerals';
59
+ import { firstSelectedDay, type CalendarSelection } from './calendar/selection';
43
60
  import PickerContainer from './container/PickerContainer';
44
61
  import {
62
+ formatDateText,
45
63
  formatMultipleText,
46
64
  formatRangeText,
47
65
  formatValueText,
66
+ type DateTextFormatter,
48
67
  } from './field/fieldText';
49
68
  import FieldMessages from './field/FieldMessages';
50
69
  import RangeFields, { type RangeEnd } from './field/RangeFields';
@@ -54,7 +73,11 @@ import { useOpenPicker } from './hooks/useOpenPicker';
54
73
  import { usePickerDraft } from './hooks/usePickerDraft';
55
74
  import { useTodayTick } from './hooks/useTodayTick';
56
75
  import TimeRows, { type PendingTimes } from './time/TimeRows';
57
- import { resolveStrings } from './theme/defaultStrings';
76
+ import {
77
+ DEFAULT_STRINGS,
78
+ compactStrings,
79
+ resolveStrings,
80
+ } from './theme/defaultStrings';
58
81
  import { buildTokens } from './theme/tokens';
59
82
  import {
60
83
  DEFAULT_YEAR_RANGE,
@@ -68,9 +91,29 @@ const sameValue = (a: DateTimeValue, b: DateTimeValue): boolean =>
68
91
  (a.time?.hour ?? -1) === (b.time?.hour ?? -1) &&
69
92
  (a.time?.minute ?? -1) === (b.time?.minute ?? -1);
70
93
 
94
+ const ALL_SYSTEMS: CalendarSystem[] = ['gregory', 'hijri', 'dual'];
95
+ const RTL = { direction: 'rtl' } as const;
96
+
97
+ /** The same kind of selection with nothing in it. */
98
+ const emptySelection = (sel: CalendarSelection): CalendarSelection =>
99
+ sel.kind === 'single'
100
+ ? { kind: 'single', day: null }
101
+ : sel.kind === 'range'
102
+ ? { kind: 'range', from: null, to: null }
103
+ : { ...sel, days: [] };
104
+
71
105
  /** Minute steps that divide the hour. */
72
106
  const MINUTE_STEPS = [1, 2, 3, 4, 5, 6, 10, 12, 15, 20, 30, 60];
73
107
 
108
+ /** A folded calendar stays mounted, so it comes back on the month and view it showed. */
109
+ const FOLDED = { display: 'none' } as const;
110
+
111
+ /** The later / earlier of a limit and a bound; the bound when there's no limit. */
112
+ const later = (a: CalendarDate | undefined, b: CalendarDate): CalendarDate =>
113
+ a && compareCalendarDates(a, b) > 0 ? a : b;
114
+ const earlier = (a: CalendarDate | undefined, b: CalendarDate): CalendarDate =>
115
+ a && compareCalendarDates(a, b) < 0 ? a : b;
116
+
74
117
  /**
75
118
  * A date & time picker: a value field that opens a sheet (or an inline panel)
76
119
  * with a calendar and, for date-time values, a time row. Single, range and
@@ -110,6 +153,7 @@ const DateTimePicker: React.FC<DateTimePickerProps> = (props) => {
110
153
  disabled = false,
111
154
  readOnly = false,
112
155
  allowTyping,
156
+ hideCalendarOnKeyboard = true,
113
157
  allowFieldTyping,
114
158
  allowClear,
115
159
  errorMessage,
@@ -141,6 +185,11 @@ const DateTimePicker: React.FC<DateTimePickerProps> = (props) => {
141
185
  initialVisibleDate,
142
186
  enableMonthSwipe = true,
143
187
  calendarSystem,
188
+ calendarSwitch,
189
+ onCalendarSystemChange,
190
+ dualDefaultTab = 'gregory',
191
+ hijriLocale = 'ar-SA',
192
+ renderCalendarSwitch,
144
193
  showToday = false,
145
194
  showHolidays = true,
146
195
  onVisibleMonthChange,
@@ -148,6 +197,7 @@ const DateTimePicker: React.FC<DateTimePickerProps> = (props) => {
148
197
  validate,
149
198
  yearRange: yearRangeProp = DEFAULT_YEAR_RANGE,
150
199
  locale,
200
+ numerals: numeralsProp,
151
201
  timeZone,
152
202
  now,
153
203
  timeFormat: timeFormatProp,
@@ -167,6 +217,7 @@ const DateTimePicker: React.FC<DateTimePickerProps> = (props) => {
167
217
  theme,
168
218
  styles,
169
219
  strings: stringOverrides,
220
+ hijriStrings: hijriOverrides,
170
221
  testID,
171
222
  } = props;
172
223
 
@@ -184,6 +235,87 @@ const DateTimePicker: React.FC<DateTimePickerProps> = (props) => {
184
235
  // Month and year picks are for single dates only.
185
236
  const precision =
186
237
  type === 'date' && mode === 'single' ? precisionProp : 'day';
238
+ // The calendars the switch offers: none for month and year picks (always
239
+ // Gregorian), or when the platform has no Hijri calendar.
240
+ const switchKey = Array.isArray(calendarSwitch)
241
+ ? calendarSwitch.join(',')
242
+ : String(!!calendarSwitch);
243
+ const switchSystems = useMemo((): CalendarSystem[] | null => {
244
+ if (!calendarSwitch || precision !== 'day' || !isHijriSupported())
245
+ return null;
246
+ const list =
247
+ calendarSwitch === true
248
+ ? ALL_SYSTEMS
249
+ : ALL_SYSTEMS.filter((x) => calendarSwitch.includes(x)).sort(
250
+ (a, b) => calendarSwitch.indexOf(a) - calendarSwitch.indexOf(b)
251
+ );
252
+ return list.length > 1 ? list : null;
253
+ // eslint-disable-next-line react-hooks/exhaustive-deps -- the list is compared by content
254
+ }, [switchKey, precision]);
255
+ const startSystem = (): CalendarSystem => {
256
+ const wanted = calendarSystem ?? 'gregory';
257
+ return switchSystems && !switchSystems.includes(wanted)
258
+ ? (switchSystems[0] ?? wanted)
259
+ : wanted;
260
+ };
261
+ // The switch changes this; a new calendarSystem prop starts over from it.
262
+ const [chosenSystem, setChosenSystem] = useState(startSystem);
263
+ // The calendar the props open on, which a value from outside counts as
264
+ // picked in. The switch's own choice coming back as the calendarSystem prop
265
+ // (onCalendarSystemChange={setSystem}) isn't a new one.
266
+ const [openedOn, setOpenedOn] = useState(startSystem);
267
+ const echo = useRef<CalendarSystem | null>(null);
268
+ useEffect(
269
+ () => {
270
+ const next = startSystem();
271
+ const own = echo.current === next;
272
+ echo.current = null;
273
+ if (own) return;
274
+ setChosenSystem(next);
275
+ setOpenedOn(next);
276
+ },
277
+ // eslint-disable-next-line react-hooks/exhaustive-deps -- only when the props change
278
+ [calendarSystem, switchKey]
279
+ );
280
+ // Month and year picks are Gregorian whatever the calendar system.
281
+ const system =
282
+ precision === 'day' ? resolveCalendarSystem(chosenSystem) : 'gregory';
283
+ // A dual calendar shows one of its two tabs; each open starts on dualDefaultTab.
284
+ const [dualTab, setDualTab] = useState(dualDefaultTab);
285
+ /** The calendar on screen. */
286
+ const shownSystem = system === 'dual' ? dualTab : system;
287
+ // A value from outside counts as picked in the calendar the props open on,
288
+ // not the one the user has since chosen in the switch.
289
+ const propSystem =
290
+ precision === 'day' ? resolveCalendarSystem(openedOn) : 'gregory';
291
+ const outsideSystem = propSystem === 'dual' ? dualDefaultTab : propSystem;
292
+ // Picks say which calendar they were made in only when one can be Hijri.
293
+ const hijriInPlay = switchSystems !== null || system !== 'gregory';
294
+ /**
295
+ * The digits for one calendar: a Hijri calendar (and a date picked in it)
296
+ * shows Arabic digits (١٢٣) in any language; a Gregorian one shows 0–9.
297
+ * `numerals` sets both.
298
+ */
299
+ const digitsFor = (sys: 'gregory' | 'hijri') => {
300
+ const kind = numeralsProp ?? (sys === 'hijri' ? 'arabic' : 'latin');
301
+ if (kind === 'arabic') return arabicDigits;
302
+ if (kind === 'latin') return latinDigits;
303
+ return localeDigits(sys === 'hijri' ? hijriLocale : loc);
304
+ };
305
+ /**
306
+ * The strings for one calendar. A Hijri one takes `hijriStrings` first, then
307
+ * AM / PM in `hijriLocale` (ص / م), then `strings`.
308
+ */
309
+ const hijriStrings = useMemo(
310
+ () => ({
311
+ ...strings,
312
+ ...hijriPeriods(hijriLocale),
313
+ ...compactStrings(hijriOverrides),
314
+ }),
315
+ [strings, hijriLocale, hijriOverrides]
316
+ );
317
+ const stringsFor = (sys: 'gregory' | 'hijri') =>
318
+ sys === 'hijri' ? hijriStrings : strings;
187
319
  const monthNames = useMemo(() => localeNames(loc).monthsLong, [loc]);
188
320
  /** A month or year pick shown as text: "March 2026" or "2026". */
189
321
  const periodText = (v: DateTimeValue | null): string => {
@@ -272,7 +404,7 @@ const DateTimePicker: React.FC<DateTimePickerProps> = (props) => {
272
404
  const {
273
405
  isOpen: sheetOpen,
274
406
  open,
275
- close,
407
+ close: closePicker,
276
408
  } = useOpenPicker({
277
409
  disabled,
278
410
  readOnly,
@@ -282,7 +414,7 @@ const DateTimePicker: React.FC<DateTimePickerProps> = (props) => {
282
414
  onClose,
283
415
  });
284
416
  const isOpen = isInline || sheetOpen;
285
- const keyboardUp = useKeyboardFold(isOpen);
417
+ const keyboardUp = useKeyboardFold(isOpen && hideCalendarOnKeyboard);
286
418
  // The picker's own boxes that have focus. Inline, another field on the
287
419
  // screen can raise the keyboard, so the calendar folds only for these.
288
420
  const [focusedParts, setFocusedParts] = useState<Record<string, boolean>>({});
@@ -349,6 +481,17 @@ const DateTimePicker: React.FC<DateTimePickerProps> = (props) => {
349
481
  },
350
482
  [loadHolidays]
351
483
  );
484
+ // A month's grid also shows days of the months either side, and a Hijri
485
+ // month can run from December into January, so a month at either end of a
486
+ // year loads the year next to it too.
487
+ const loadHolidaysAround = useCallback(
488
+ (year: number, month: number) => {
489
+ loadHolidaysFor(year);
490
+ if (month >= 11 && year < yearRange.last) loadHolidaysFor(year + 1);
491
+ if (month === 1 && year > yearRange.first) loadHolidaysFor(year - 1);
492
+ },
493
+ [loadHolidaysFor, yearRange.first, yearRange.last]
494
+ );
352
495
  const holidays = useMemo(
353
496
  () => [...(holidaysProp ?? []), ...Object.values(loadedHolidays).flat()],
354
497
  [holidaysProp, loadedHolidays]
@@ -357,11 +500,15 @@ const DateTimePicker: React.FC<DateTimePickerProps> = (props) => {
357
500
  // Limits are worked out when the picker opens (the screen's own validation
358
501
  // after Confirm stays the final check).
359
502
  const weekdaysKey = disabledWeekdays?.join(',');
503
+ // Limits built with `dependsOnNow` ("from now on") are rebuilt every minute
504
+ // while the picker is open.
505
+ const [minuteTick, setMinuteTick] = useState(0);
360
506
  const constraints = useMemo(() => {
361
507
  const nowDate = now?.() ?? new Date();
508
+ // yearRange bounds the limits too, so days outside it are greyed.
362
509
  const limits = {
363
- minDate,
364
- maxDate,
510
+ minDate: later(minDate, { year: yearRange.first, month: 1, day: 1 }),
511
+ maxDate: earlier(maxDate, { year: yearRange.last, month: 12, day: 31 }),
365
512
  disabledDates,
366
513
  enabledDates,
367
514
  timeBounds,
@@ -383,8 +530,8 @@ const DateTimePicker: React.FC<DateTimePickerProps> = (props) => {
383
530
  type,
384
531
  timeZone: zone,
385
532
  now: nowDate,
386
- minDate,
387
- maxDate,
533
+ minDate: limits.minDate,
534
+ maxDate: limits.maxDate,
388
535
  isDisabled: disabledDates,
389
536
  isEnabled: enabledDates,
390
537
  timeBounds,
@@ -392,10 +539,11 @@ const DateTimePicker: React.FC<DateTimePickerProps> = (props) => {
392
539
  holidays,
393
540
  disableHolidays,
394
541
  });
395
- // eslint-disable-next-line react-hooks/exhaustive-deps -- `isOpen` and `today` refresh the limits on open and at midnight; weekdays are compared by content
542
+ // eslint-disable-next-line react-hooks/exhaustive-deps -- `isOpen`, `today` and `minuteTick` refresh the limits on open, at midnight and each minute; weekdays are compared by content
396
543
  }, [
397
544
  isOpen,
398
545
  today,
546
+ minuteTick,
399
547
  type,
400
548
  zone,
401
549
  now,
@@ -408,8 +556,27 @@ const DateTimePicker: React.FC<DateTimePickerProps> = (props) => {
408
556
  weekdaysKey,
409
557
  disableHolidays,
410
558
  getConstraints,
559
+ yearRange,
411
560
  ]);
412
561
 
562
+ const followsClock = isOpen && constraints.dependsOnNow;
563
+ useEffect(() => {
564
+ if (!followsClock) return;
565
+ let timer: ReturnType<typeof setTimeout> | undefined;
566
+ const arm = () => {
567
+ const ms = 60_000 - ((now?.() ?? new Date()).getTime() % 60_000);
568
+ timer = setTimeout(() => {
569
+ setMinuteTick((n) => n + 1);
570
+ arm();
571
+ }, ms);
572
+ // Under Node (e.g. Jest) the timer must not keep the process alive.
573
+ (timer as { unref?: () => void }).unref?.();
574
+ };
575
+ arm();
576
+ return () => clearTimeout(timer);
577
+ // eslint-disable-next-line react-hooks/exhaustive-deps -- armed while open with a clock-bound limit
578
+ }, [followsClock]);
579
+
413
580
  // A typed field that doesn't parse blocks Confirm, per field: one end of a
414
581
  // range can't hide the other end's problem.
415
582
  const [typingBlocks, setTypingBlocks] = useState<
@@ -444,14 +611,30 @@ const DateTimePicker: React.FC<DateTimePickerProps> = (props) => {
444
611
  const [wheelSpinning, setWheelSpinning] = useState(false);
445
612
  // From/To layout: the end that calendar taps fill. perDate: the day the time row edits.
446
613
  const [activeEnd, setActiveEnd] = useState<RangeEnd>('from');
614
+ // From/To layout: a From or To field was tapped, so the next day tap edits that end only.
615
+ const [endPicked, setEndPicked] = useState(false);
616
+ const activateEnd = useCallback((end: RangeEnd) => {
617
+ setActiveEnd(end);
618
+ setEndPicked(true);
619
+ }, []);
447
620
  const [activeDay, setActiveDay] = useState<CalendarDate | null>(null);
448
621
  const resetSelectionUi = useCallback(() => {
622
+ // What was typed is gone with the selection, so nothing blocks Confirm.
623
+ setTypingBlocks({});
449
624
  setPendingTimes({});
450
625
  setActiveEnd('from');
626
+ setEndPicked(false);
451
627
  setActiveDay(null);
452
628
  }, []);
629
+ // A day in the month the calendar last showed, so a switch to the other
630
+ // calendar opens on the same days. Each open starts from the value again.
631
+ const shownAnchor = useRef<CalendarDate | null>(null);
632
+ const rememberAnchor = useCallback((day: CalendarDate) => {
633
+ shownAnchor.current = day;
634
+ }, []);
453
635
  // Opening or closing starts fresh: nothing edited, nothing blocked.
454
636
  useEffect(() => {
637
+ shownAnchor.current = null;
455
638
  edited.current = false;
456
639
  setTypingBlocks({});
457
640
  setTimeCheck(0);
@@ -529,6 +712,63 @@ const DateTimePicker: React.FC<DateTimePickerProps> = (props) => {
529
712
  latest.current = { validate, onError, value };
530
713
  const valueKey = JSON.stringify(value ?? '');
531
714
 
715
+ // A selection belongs to the calendar it was picked in (Gregorian or Hijri):
716
+ // it shows only there, and the field writes it in that calendar. A saved
717
+ // value remembers its calendar; one from outside counts as picked in the
718
+ // calendar the picker opens on.
719
+ // The saved value as a draft, for the on-screen field and for comparing
720
+ // values by what they hold.
721
+ const saved = useMemo(
722
+ () => createDraft(mode, value, draftOptions),
723
+ // eslint-disable-next-line react-hooks/exhaustive-deps -- the value is compared by content (`valueKey`)
724
+ [valueKey, mode, draftOptions]
725
+ );
726
+ /** A draft's days and times, so a value written another way ({to, from}) still matches. */
727
+ const contentOf = (dr: Draft): string => JSON.stringify(valuesOf(dr));
728
+ const savedContent = contentOf(saved);
729
+ const savedSystemRef = useRef<{
730
+ content: string;
731
+ system: 'gregory' | 'hijri';
732
+ } | null>(null);
733
+ const savedSystem =
734
+ savedSystemRef.current?.content === savedContent
735
+ ? savedSystemRef.current.system
736
+ : outsideSystem;
737
+ const [draftSystem, setDraftSystem] = useState(savedSystem);
738
+ // The draft is rebuilt on open and on a new value; so is its calendar. A new
739
+ // calendarSystem prop while the user is picking starts the selection over,
740
+ // since a selection is one calendar's.
741
+ const systemSeen = useRef({ isOpen, valueKey, savedSystem });
742
+ useEffect(() => {
743
+ const seen = systemSeen.current;
744
+ systemSeen.current = { isOpen, valueKey, savedSystem };
745
+ setDraftSystem(savedSystem);
746
+ const systemOnly =
747
+ seen.isOpen === isOpen &&
748
+ seen.valueKey === valueKey &&
749
+ seen.savedSystem !== savedSystem;
750
+ if (systemOnly && isOpen && edited.current) {
751
+ draft.clear();
752
+ resetSelectionUi();
753
+ }
754
+ // eslint-disable-next-line react-hooks/exhaustive-deps -- the draft's actions are stable
755
+ }, [isOpen, valueKey, savedSystem]);
756
+ useEffect(() => setDualTab(dualDefaultTab), [isOpen, dualDefaultTab]);
757
+ const selectionShown = draftSystem === shownSystem;
758
+ /** Saves a value: with the calendar it was picked in, when one can be Hijri. */
759
+ const emitChange = useCallback(
760
+ (wire: PickerValue, picked: 'gregory' | 'hijri') => {
761
+ savedSystemRef.current = {
762
+ content: contentOf(createDraft(mode, wire, draftOptions)),
763
+ system: picked,
764
+ };
765
+ if (hijriInPlay) onChange(wire, { calendarSystem: picked });
766
+ else onChange(wire);
767
+ },
768
+ // eslint-disable-next-line react-hooks/exhaustive-deps -- contentOf reads only `type` and the options listed
769
+ [hijriInPlay, onChange, mode, draftOptions]
770
+ );
771
+
532
772
  /**
533
773
  * Whether a draft can be saved as it stands: complete, every day allowed,
534
774
  * every time allowed and on the minute step, and nothing being typed or
@@ -555,18 +795,18 @@ const DateTimePicker: React.FC<DateTimePickerProps> = (props) => {
555
795
  // Every save: Confirm, an 'instant' edit (onConfirm is added by `confirm`).
556
796
  const commit = useCallback(
557
797
  (wire: PickerValue) => {
558
- onChange(wire);
798
+ emitChange(wire, draftSystem);
559
799
  onBlur?.();
560
800
  },
561
- [onChange, onBlur]
801
+ [emitChange, draftSystem, onBlur]
562
802
  );
563
803
 
564
804
  const confirm = useCallback(() => {
565
805
  if (!canConfirm) return;
566
- const { validate: check, onError: report, value: saved } = latest.current;
806
+ const { validate: check, onError: report, value: current } = latest.current;
567
807
  if (timeBlocked) {
568
808
  setTimeCheck((n) => n + 1);
569
- report?.(timeIssue ?? 'TIME_NOT_ALLOWED', saved);
809
+ report?.(timeIssue ?? 'TIME_NOT_ALLOWED', current);
570
810
  return;
571
811
  }
572
812
  const wire = draftToWire(preview, draftOptions);
@@ -579,7 +819,7 @@ const DateTimePicker: React.FC<DateTimePickerProps> = (props) => {
579
819
  commit(wire);
580
820
  onConfirm?.(wire);
581
821
  edited.current = false;
582
- if (!isInline) close();
822
+ if (!isInline) closePicker();
583
823
  }, [
584
824
  canConfirm,
585
825
  timeBlocked,
@@ -589,15 +829,15 @@ const DateTimePicker: React.FC<DateTimePickerProps> = (props) => {
589
829
  preview,
590
830
  draftOptions,
591
831
  isInline,
592
- close,
832
+ closePicker,
593
833
  ]);
594
834
 
595
835
  const clearValue = useCallback(() => {
596
836
  draft.clear();
597
837
  resetSelectionUi();
598
838
  edited.current = false;
599
- onChange('');
600
- }, [draft, onChange, resetSelectionUi]);
839
+ emitChange('', draftSystem);
840
+ }, [draft, emitChange, draftSystem, resetSelectionUi]);
601
841
 
602
842
  const clearDraft = useCallback(() => {
603
843
  edited.current = true;
@@ -607,10 +847,11 @@ const DateTimePicker: React.FC<DateTimePickerProps> = (props) => {
607
847
 
608
848
  // × in the field above the calendar. 'instant' saves every edit, so it
609
849
  // clears the saved value too (once; an empty value isn't saved again).
850
+ const hasSaved = valuesOf(saved).some((v) => v !== null);
610
851
  const clearInSheet = useCallback(() => {
611
- if (commitMode === 'instant' && valueKey !== '""') clearValue();
852
+ if (commitMode === 'instant' && hasSaved) clearValue();
612
853
  else clearDraft();
613
- }, [commitMode, valueKey, clearValue, clearDraft]);
854
+ }, [commitMode, hasSaved, clearValue, clearDraft]);
614
855
 
615
856
  // Opening or closing starts without the last `validate` message.
616
857
  useEffect(() => setValidation(null), [isOpen]);
@@ -621,15 +862,32 @@ const DateTimePicker: React.FC<DateTimePickerProps> = (props) => {
621
862
  // An edit equal to the saved value isn't saved again; one `validate`
622
863
  // rejected waits until the selection or the saved value changes.
623
864
  const lastRejected = useRef<string | null>(null);
865
+ // The last value saved: not saved again while the parent's `value` catches
866
+ // up (a slow store, a server round trip) or when it never does.
867
+ const lastEmitted = useRef<string | null>(null);
624
868
  useEffect(() => {
625
869
  lastRejected.current = null;
626
- }, [valueKey]);
627
- useEffect(() => {
870
+ lastEmitted.current = null;
871
+ }, [valueKey, isOpen]);
872
+ // The field in the sheet saves once typing there is done (focus leaves it),
873
+ // so 09:30 isn't saved as 09:00 and 09:03 on the way.
874
+ const sheetTyping = !!(
875
+ focusedParts.value ||
876
+ focusedParts['value-from'] ||
877
+ focusedParts['value-to']
878
+ );
879
+ /** Saves the draft in instant mode; `typingDone` once the sheet's field is left. */
880
+ const saveInstant = (typingDone: boolean) => {
628
881
  if (commitMode !== 'instant' || !isOpen || !edited.current) return;
629
- if (timeIssue || !canSave(draft.draft)) return;
882
+ if (!typingDone || timeIssue || !canSave(draft.draft)) return;
630
883
  const wire = draft.toWire();
631
884
  const key = JSON.stringify(wire);
632
- if (key === valueKey || key === lastRejected.current) return;
885
+ if (key === lastRejected.current || key === lastEmitted.current) return;
886
+ // The same dates picked in the same calendar aren't saved again; the same
887
+ // dates picked in the other calendar are.
888
+ const unchanged =
889
+ key === valueKey || contentOf(draft.draft) === savedContent;
890
+ if (unchanged && draftSystem === savedSystem) return;
633
891
  const message = latest.current.validate?.(wire);
634
892
  if (message) {
635
893
  lastRejected.current = key;
@@ -637,18 +895,35 @@ const DateTimePicker: React.FC<DateTimePickerProps> = (props) => {
637
895
  latest.current.onError?.('VALIDATION', wire);
638
896
  return;
639
897
  }
898
+ lastEmitted.current = key;
640
899
  commit(wire);
900
+ };
901
+ // Only typing waits for the field to be left; a tap is a finished edit.
902
+ const typedLast = useRef(false);
903
+ useEffect(
904
+ () => saveInstant(!sheetTyping || !typedLast.current),
641
905
  // eslint-disable-next-line react-hooks/exhaustive-deps -- when the draft or what blocks it changes, not when callbacks are re-created
642
- }, [
643
- commitMode,
644
- isOpen,
645
- draft.draft,
646
- timeIssue,
647
- typingBlocked,
648
- wheelSpinning,
649
- confirmDisabled,
650
- constraints,
651
- ]);
906
+ [
907
+ commitMode,
908
+ isOpen,
909
+ sheetTyping,
910
+ draft.draft,
911
+ draftSystem,
912
+ timeIssue,
913
+ typingBlocked,
914
+ wheelSpinning,
915
+ confirmDisabled,
916
+ constraints,
917
+ ]
918
+ );
919
+ // Closing while the sheet's field still has focus ends the typing: an
920
+ // instant picker saves what was typed before it closes.
921
+ const saveInstantRef = useRef(saveInstant);
922
+ saveInstantRef.current = saveInstant;
923
+ const close = useCallback(() => {
924
+ saveInstantRef.current(true);
925
+ closePicker();
926
+ }, [closePicker]);
652
927
 
653
928
  // A typed date with no time yet gets one the way a tapped day does: the fixed
654
929
  // newDayTime, else a time already typed in the time row, else now.
@@ -664,8 +939,16 @@ const DateTimePicker: React.FC<DateTimePickerProps> = (props) => {
664
939
  }
665
940
  : null);
666
941
 
942
+ // Typed segments are Gregorian, so typing keeps the calendar a selection was
943
+ // picked in; into an empty draft, it takes the calendar on screen.
944
+ const adoptShownIfEmpty = () => {
945
+ if (valuesOf(d).every((v) => v === null)) setDraftSystem(shownSystem);
946
+ };
947
+
667
948
  const onSheetTyped = (parsed: ParsedSegments) => {
668
949
  edited.current = true;
950
+ typedLast.current = true;
951
+ adoptShownIfEmpty();
669
952
  const typed = withFilledTime(parsed, 'single');
670
953
  setTypingBlock('single', !parsed.isEmpty && !typed);
671
954
  if (typed) draft.setValue(typed, 'single');
@@ -674,6 +957,8 @@ const DateTimePicker: React.FC<DateTimePickerProps> = (props) => {
674
957
 
675
958
  const onRangeTyped = (end: RangeEnd, parsed: ParsedSegments) => {
676
959
  edited.current = true;
960
+ typedLast.current = true;
961
+ adoptShownIfEmpty();
677
962
  const typed = withFilledTime(parsed, end);
678
963
  setTypingBlock(end, !parsed.isEmpty && !typed);
679
964
  if (typed || parsed.isEmpty) draft.setValue(typed, end);
@@ -692,35 +977,111 @@ const DateTimePicker: React.FC<DateTimePickerProps> = (props) => {
692
977
  return { hour: Math.floor(capped / 60), minute: capped % 60 };
693
978
  }, [now, zone, minuteStep]);
694
979
 
980
+ // A range end picked on the start's day starts an hour after the start (at
981
+ // most the day's latest allowed time, or its last step), so the range isn't
982
+ // out of order before the user has touched the time.
983
+ // A time typed for the end before its day is kept as typed.
984
+ const endTimeFor = (
985
+ from: DateTimeValue | null,
986
+ day: CalendarDate,
987
+ typed: Time | null | undefined
988
+ ): Time => {
989
+ if (!fixedTime && typed) return typed;
990
+ const time = fixedTime ?? nowTime();
991
+ if (!from?.time || !isSameDay(from.date, day)) return time;
992
+ const start = from.time.hour * 60 + from.time.minute;
993
+ if (time.hour * 60 + time.minute > start) return time;
994
+ // Within the day's allowed hours when it has them.
995
+ const max = constraints.timeBounds(day)?.max;
996
+ const last = max
997
+ ? max.hour * 60 + max.minute
998
+ : 24 * 60 - Math.max(1, minuteStep);
999
+ const end = Math.min(start + 60, last);
1000
+ return { hour: Math.floor(end / 60), minute: end % 60 };
1001
+ };
1002
+
695
1003
  const onSelectDay = useCallback(
696
1004
  (day: CalendarDate) => {
697
1005
  edited.current = true;
698
- if (d.mode === 'range') {
1006
+ typedLast.current = false;
1007
+ // A pick in the other calendar starts over: the selection is one calendar's.
1008
+ const fresh =
1009
+ draftSystem !== shownSystem && valuesOf(d).some((v) => v !== null);
1010
+ setDraftSystem(shownSystem);
1011
+ const cur = fresh ? emptied(d) : d;
1012
+ const end0: RangeEnd = fresh ? 'from' : activeEnd;
1013
+ const dayInEdit = fresh ? null : activeDay;
1014
+ const times: PendingTimes = fresh ? {} : pending;
1015
+ if (fresh) {
1016
+ draft.clear();
1017
+ resetSelectionUi();
1018
+ }
1019
+ if (cur.mode === 'range') {
699
1020
  if (rangeLayout === 'fromTo') {
1021
+ setEndPicked(false);
1022
+ // A full range: the tap starts a new one (or moves the nearest end).
1023
+ if (!endPicked && cur.from.value && cur.to.value) {
1024
+ const moveNearest = range?.reselect === 'moveNearestEnd';
1025
+ const nearest: RangeEnd =
1026
+ Math.abs(daysBetween(cur.from.value.date, day)) <
1027
+ Math.abs(daysBetween(cur.to.value.date, day))
1028
+ ? 'from'
1029
+ : 'to';
1030
+ const end = moveNearest ? nearest : 'from';
1031
+ setTypingBlock('from', false);
1032
+ setTypingBlock('to', false);
1033
+ draft.selectDay(day, fixedTime ?? times[end] ?? nowTime());
1034
+ setActiveEnd(moveNearest ? nearest : 'to');
1035
+ return;
1036
+ }
1037
+ // Choosing the end after a start tap: a day before the start moves the start.
1038
+ const end1: RangeEnd =
1039
+ end0 === 'to' &&
1040
+ !endPicked &&
1041
+ cur.from.value &&
1042
+ compareCalendarDates(day, cur.from.value.date) < 0
1043
+ ? 'from'
1044
+ : end0;
700
1045
  // The tapped day replaces whatever was typed in this end only.
701
- setTypingBlock(activeEnd, false);
1046
+ setTypingBlock(end1, false);
702
1047
  draft.setRangeEnd(
703
1048
  day,
704
- activeEnd,
705
- fixedTime ?? pending[activeEnd] ?? nowTime()
1049
+ end1,
1050
+ end1 === 'to'
1051
+ ? endTimeFor(cur.from.value, day, times.to)
1052
+ : (fixedTime ?? times.from ?? nowTime())
706
1053
  );
707
- if (activeEnd === 'from') setActiveEnd('to');
1054
+ // The start tapped again where a range can't be one day deselects
1055
+ // it, so the next tap picks the start again.
1056
+ const deselects =
1057
+ end1 === 'to' &&
1058
+ !sameDayRangeOk(draftOptions) &&
1059
+ isSameDay(cur.from.value?.date, day);
1060
+ if (end0 === 'from') setActiveEnd('to');
1061
+ else if (deselects) setActiveEnd('from');
708
1062
  return;
709
1063
  }
710
1064
  // The single field: the first tap is the start, the next one the end.
711
1065
  const startsOver =
712
- !d.from.value ||
713
- (!!d.to.value && range?.reselect !== 'moveNearestEnd');
1066
+ !cur.from.value ||
1067
+ (!!cur.to.value && range?.reselect !== 'moveNearestEnd');
714
1068
  const end: RangeEnd = startsOver ? 'from' : 'to';
715
1069
  setTypingBlock('single', false);
716
- draft.selectDay(day, fixedTime ?? pending[end] ?? nowTime());
1070
+ draft.selectDay(
1071
+ day,
1072
+ end === 'to' && !cur.to.value
1073
+ ? endTimeFor(cur.from.value, day, times.to)
1074
+ : (fixedTime ?? times[end] ?? nowTime())
1075
+ );
717
1076
  return;
718
1077
  }
719
- if (d.mode === 'multiple') {
1078
+ if (cur.mode === 'multiple') {
720
1079
  const perDate = type === 'datetime' && multipleTimeMode === 'perDate';
721
- const picked = d.slots.some((slot) => isSameDay(slot.value?.date, day));
1080
+ const picked = cur.slots.some((slot) =>
1081
+ isSameDay(slot.value?.date, day)
1082
+ );
722
1083
  // perDate: a tap on a picked day first makes it the one the time row edits.
723
- if (perDate && picked && !isSameDay(activeDay, day)) {
1084
+ if (perDate && picked && !isSameDay(dayInEdit, day)) {
724
1085
  setActiveDay(day);
725
1086
  return;
726
1087
  }
@@ -729,8 +1090,9 @@ const DateTimePicker: React.FC<DateTimePickerProps> = (props) => {
729
1090
  return;
730
1091
  }
731
1092
  setTypingBlock('single', false);
732
- draft.selectDay(day, fixedTime ?? pending.single ?? nowTime());
1093
+ draft.selectDay(day, fixedTime ?? times.single ?? nowTime());
733
1094
  },
1095
+ // eslint-disable-next-line react-hooks/exhaustive-deps -- valuesOf reads only `d` and props listed here
734
1096
  [
735
1097
  d,
736
1098
  draft,
@@ -744,21 +1106,39 @@ const DateTimePicker: React.FC<DateTimePickerProps> = (props) => {
744
1106
  type,
745
1107
  multipleTimeMode,
746
1108
  setTypingBlock,
1109
+ draftSystem,
1110
+ shownSystem,
1111
+ resetSelectionUi,
1112
+ endPicked,
1113
+ draftOptions,
1114
+ minuteStep,
1115
+ constraints,
747
1116
  ]
748
1117
  );
749
1118
 
1119
+ // The calendar gets a tap handler that never changes, so its memoized day
1120
+ // cells don't all redraw on every tap; it calls the current one.
1121
+ const selectDayRef = useRef(onSelectDay);
1122
+ selectDayRef.current = onSelectDay;
1123
+ const onSelectDayStable = useCallback(
1124
+ (day: CalendarDate) => selectDayRef.current(day),
1125
+ []
1126
+ );
1127
+
750
1128
  // Greying that depends on what's picked: range length, From/To order, the multiple cap.
751
1129
  const calendarConstraints = useMemo(() => {
752
1130
  if (d.mode === 'range') {
1131
+ // A full From/To range greys nothing: the next tap starts a new one.
753
1132
  const choosingEnd =
754
1133
  rangeLayout === 'fromTo'
755
- ? activeEnd === 'to'
1134
+ ? activeEnd === 'to' && (endPicked || !d.to.value)
756
1135
  : !!d.from.value && !d.to.value;
757
1136
  return withSelectionRules(constraints, {
758
1137
  rangeStart: choosingEnd ? d.from.value?.date : null,
759
1138
  minDays: range?.minDays,
760
1139
  maxDays: range?.maxDays,
761
- blockBeforeStart: rangeLayout === 'fromTo',
1140
+ // Only an end chosen from the To field can't go before the start.
1141
+ blockBeforeStart: rangeLayout === 'fromTo' && endPicked,
762
1142
  });
763
1143
  }
764
1144
  if (d.mode === 'multiple') {
@@ -775,6 +1155,7 @@ const DateTimePicker: React.FC<DateTimePickerProps> = (props) => {
775
1155
  d,
776
1156
  rangeLayout,
777
1157
  activeEnd,
1158
+ endPicked,
778
1159
  range?.minDays,
779
1160
  range?.maxDays,
780
1161
  multiple?.maxSelections,
@@ -812,15 +1193,16 @@ const DateTimePicker: React.FC<DateTimePickerProps> = (props) => {
812
1193
 
813
1194
  const handleVisibleMonth = useCallback(
814
1195
  (year: number, month: number) => {
815
- loadHolidaysFor(year);
1196
+ loadHolidaysAround(year, month);
816
1197
  onVisibleMonthChange?.(year, month);
817
1198
  },
818
- [loadHolidaysFor, onVisibleMonthChange]
1199
+ [loadHolidaysAround, onVisibleMonthChange]
819
1200
  );
820
1201
  useEffect(() => {
821
1202
  if (!isOpen) return;
822
- const anchor = selection.kind === 'single' ? selection.day : null;
823
- loadHolidaysFor((anchor ?? today).year);
1203
+ const anchor =
1204
+ (selection.kind === 'single' ? selection.day : null) ?? today;
1205
+ loadHolidaysAround(anchor.year, anchor.month);
824
1206
  // eslint-disable-next-line react-hooks/exhaustive-deps -- once per open
825
1207
  }, [isOpen]);
826
1208
 
@@ -874,17 +1256,28 @@ const DateTimePicker: React.FC<DateTimePickerProps> = (props) => {
874
1256
  return;
875
1257
  }
876
1258
  setTriggerError('');
877
- onChange(wire);
1259
+ // Typing keeps the calendar the saved value was picked in.
1260
+ emitChange(wire, savedSystem);
878
1261
  onBlur?.();
879
1262
  };
880
1263
 
1264
+ /** A value's date in the calendar it was picked in: Hijri text, or none for the dateFormat. */
1265
+ const dateTextFor = (
1266
+ picked: 'gregory' | 'hijri'
1267
+ ): DateTextFormatter | undefined =>
1268
+ picked === 'hijri'
1269
+ ? (v) =>
1270
+ hijriDateText(v.date, hijriLocale) ?? formatDateText(v, dateFormat)
1271
+ : undefined;
1272
+ const savedDateText = dateTextFor(savedSystem);
1273
+ const draftDateText = dateTextFor(draftSystem);
1274
+ const savedStrings = stringsFor(savedSystem);
1275
+ const draftStrings = stringsFor(draftSystem);
1276
+ // Error messages inside the picker follow the tab on screen; the on-screen
1277
+ // field's keep `strings`.
1278
+ const shownStrings = stringsFor(shownSystem);
1279
+
881
1280
  // What the on-screen field shows: the saved value, never the draft.
882
- const savedKey = valueKey;
883
- const saved = useMemo(
884
- () => createDraft(mode, value, draftOptions),
885
- // eslint-disable-next-line react-hooks/exhaustive-deps -- the value is compared by content (`savedKey`)
886
- [savedKey, mode, draftOptions]
887
- );
888
1281
  const savedSingle = saved.mode === 'single' ? saved.slot.value : null;
889
1282
  const savedText =
890
1283
  saved.mode === 'range'
@@ -894,14 +1287,18 @@ const DateTimePicker: React.FC<DateTimePickerProps> = (props) => {
894
1287
  type,
895
1288
  dateFormat,
896
1289
  timeFormat,
897
- strings.rangeSeparator
1290
+ strings.rangeSeparator,
1291
+ savedDateText,
1292
+ savedStrings
898
1293
  )
899
1294
  : saved.mode === 'multiple'
900
1295
  ? formatMultipleText(
901
1296
  saved.slots.map((slot) => slot.value),
902
1297
  type,
903
1298
  dateFormat,
904
- timeFormat
1299
+ timeFormat,
1300
+ savedDateText,
1301
+ savedStrings
905
1302
  )
906
1303
  : precision !== 'day'
907
1304
  ? periodText(savedSingle)
@@ -954,8 +1351,17 @@ const DateTimePicker: React.FC<DateTimePickerProps> = (props) => {
954
1351
  type,
955
1352
  mode,
956
1353
  value,
957
- displayText:
958
- savedText ?? formatValueText(savedSingle, type, dateFormat, timeFormat),
1354
+ displayText: digitsFor(savedSystem)(
1355
+ savedText ??
1356
+ formatValueText(
1357
+ savedSingle,
1358
+ type,
1359
+ dateFormat,
1360
+ timeFormat,
1361
+ savedDateText,
1362
+ savedStrings
1363
+ )
1364
+ ),
959
1365
  disabled,
960
1366
  readOnly,
961
1367
  isOpen,
@@ -981,7 +1387,7 @@ const DateTimePicker: React.FC<DateTimePickerProps> = (props) => {
981
1387
  draft.draft.mode === 'single' ? draft.draft.slot.value : null;
982
1388
  // A time outside the limits is explained under the time row, not here.
983
1389
  const sheetValueError =
984
- sheetValue && dayOff(sheetValue.date) ? strings.errorNotAllowed : '';
1390
+ sheetValue && dayOff(sheetValue.date) ? shownStrings.errorNotAllowed : '';
985
1391
 
986
1392
  let rangeError = '';
987
1393
  if (d.mode === 'range' && d.from.value && d.to.value) {
@@ -990,14 +1396,14 @@ const DateTimePicker: React.FC<DateTimePickerProps> = (props) => {
990
1396
  constraints.dayStatus(d.from.value.date).disabled ||
991
1397
  constraints.dayStatus(d.to.value.date).disabled
992
1398
  )
993
- rangeError = strings.errorNotAllowed;
1399
+ rangeError = shownStrings.errorNotAllowed;
994
1400
  else if (previewIssue === 'RANGE_ORDER')
995
- rangeError = strings.errorRangeOrder;
1401
+ rangeError = shownStrings.errorRangeOrder;
996
1402
  else if (previewIssue === 'RANGE_LENGTH')
997
1403
  rangeError =
998
1404
  range?.minDays !== undefined && length < range.minDays
999
- ? strings.errorRangeTooShort(range.minDays)
1000
- : strings.errorRangeTooLong(range?.maxDays ?? length);
1405
+ ? shownStrings.errorRangeTooShort(range.minDays)
1406
+ : shownStrings.errorRangeTooLong(range?.maxDays ?? length);
1001
1407
  }
1002
1408
 
1003
1409
  const fieldCommon = {
@@ -1019,8 +1425,11 @@ const DateTimePicker: React.FC<DateTimePickerProps> = (props) => {
1019
1425
  ) : (
1020
1426
  <ValueField
1021
1427
  {...fieldCommon}
1428
+ strings={savedStrings}
1429
+ errorStrings={strings}
1022
1430
  value={savedSingle}
1023
1431
  displayText={savedText}
1432
+ dateText={savedDateText}
1024
1433
  allowTyping={
1025
1434
  mode === 'single' && precision === 'day' && allowFieldTyping
1026
1435
  }
@@ -1062,8 +1471,11 @@ const DateTimePicker: React.FC<DateTimePickerProps> = (props) => {
1062
1471
  ) : mode === 'single' ? (
1063
1472
  <ValueField
1064
1473
  {...fieldCommon}
1474
+ strings={draftStrings}
1475
+ errorStrings={shownStrings}
1065
1476
  value={sheetValue}
1066
1477
  displayText={precision !== 'day' ? periodText(sheetValue) : undefined}
1478
+ dateText={draftDateText}
1067
1479
  placeholder={sheetOptions?.fieldPlaceholder ?? periodPlaceholder}
1068
1480
  allowTyping={allowTyping && precision === 'day'}
1069
1481
  onTyped={onSheetTyped}
@@ -1078,13 +1490,16 @@ const DateTimePicker: React.FC<DateTimePickerProps> = (props) => {
1078
1490
  ) : d.mode === 'range' ? (
1079
1491
  <RangeFields
1080
1492
  {...fieldCommon}
1493
+ strings={draftStrings}
1494
+ errorStrings={shownStrings}
1081
1495
  layout={rangeLayout}
1496
+ dateText={draftDateText}
1082
1497
  showIcon={sheetOptions?.fieldIcon}
1083
1498
  from={d.from.value}
1084
1499
  to={d.to.value}
1085
1500
  placeholder={sheetOptions?.fieldPlaceholder}
1086
1501
  activeEnd={activeEnd}
1087
- onActivate={setActiveEnd}
1502
+ onActivate={activateEnd}
1088
1503
  allowTyping={allowTyping}
1089
1504
  onTyped={onRangeTyped}
1090
1505
  onFocusChange={(end, f) => reportFocus(`value-${end}`, f)}
@@ -1097,12 +1512,15 @@ const DateTimePicker: React.FC<DateTimePickerProps> = (props) => {
1097
1512
  ) : d.mode === 'multiple' ? (
1098
1513
  <ValueField
1099
1514
  {...fieldCommon}
1515
+ strings={draftStrings}
1100
1516
  value={null}
1101
1517
  displayText={formatMultipleText(
1102
1518
  draftValues,
1103
1519
  type,
1104
1520
  dateFormat,
1105
- timeFormat
1521
+ timeFormat,
1522
+ draftDateText,
1523
+ draftStrings
1106
1524
  )}
1107
1525
  placeholder={sheetOptions?.fieldPlaceholder}
1108
1526
  allowClear={allowClear}
@@ -1114,41 +1532,114 @@ const DateTimePicker: React.FC<DateTimePickerProps> = (props) => {
1114
1532
  />
1115
1533
  ) : null;
1116
1534
 
1117
- const calendar = folded ? null : (
1118
- <Calendar
1119
- constraints={calendarConstraints}
1120
- today={today}
1121
- initialVisibleDate={initialVisibleDate}
1122
- selection={selection}
1123
- onSelectDay={onSelectDay}
1124
- firstDayOfWeek={firstDayOfWeek}
1125
- showOutsideDays={showOutsideDays}
1126
- showHolidays={showHolidays}
1127
- yearRange={yearRange}
1128
- locale={loc}
1129
- // Month and year picks are Gregorian whatever the calendar system.
1130
- calendarSystem={
1131
- precision === 'day' ? resolveCalendarSystem(calendarSystem) : 'gregory'
1132
- }
1133
- initialView={initialView}
1134
- precision={precision}
1135
- enableMonthSwipe={enableMonthSwipe}
1136
- showToday={showToday}
1137
- onVisibleMonthChange={handleVisibleMonth}
1138
- renderDay={renderDay}
1139
- renderWeekday={renderWeekday}
1140
- renderMonthChip={renderMonthChip}
1141
- renderYearCell={renderYearCell}
1142
- renderMonthCell={renderMonthCell}
1143
- renderEmptyState={renderEmptyState}
1144
- icons={icons}
1145
- iconProps={iconProps}
1535
+ const systemLabels: Record<CalendarSystem, string> = {
1536
+ gregory: strings.calendarGregorian ?? DEFAULT_STRINGS.calendarGregorian,
1537
+ hijri: strings.calendarHijri ?? DEFAULT_STRINGS.calendarHijri,
1538
+ dual: strings.calendarDual ?? DEFAULT_STRINGS.calendarDual,
1539
+ };
1540
+ const systemA11yLabel =
1541
+ strings.a11yCalendarSystem ?? DEFAULT_STRINGS.a11yCalendarSystem;
1542
+ const chooseSystem = (next: CalendarSystem) => {
1543
+ if (next === system) return;
1544
+ setChosenSystem(next);
1545
+ if (next === 'dual') setDualTab(dualDefaultTab);
1546
+ echo.current = next;
1547
+ onCalendarSystemChange?.(next);
1548
+ };
1549
+ const calendarSwitchNode = !switchSystems ? null : renderCalendarSwitch ? (
1550
+ renderCalendarSwitch({
1551
+ systems: switchSystems,
1552
+ selected: system,
1553
+ onSelect: chooseSystem,
1554
+ tokens,
1555
+ strings,
1556
+ })
1557
+ ) : (
1558
+ <CalendarSwitch
1559
+ variant="switch"
1560
+ options={switchSystems.map((v) => {
1561
+ return { value: v, label: systemLabels[v] };
1562
+ })}
1563
+ selected={system}
1564
+ onSelect={chooseSystem}
1565
+ accessibilityLabel={systemA11yLabel}
1146
1566
  tokens={tokens}
1147
1567
  styles={styles}
1148
- strings={strings}
1149
- testID={testID}
1568
+ testID={testID ? `${testID}-calendar-switch` : undefined}
1150
1569
  />
1151
1570
  );
1571
+ const dualTabs =
1572
+ system !== 'dual' ? null : (
1573
+ <CalendarSwitch
1574
+ variant="tabs"
1575
+ options={[
1576
+ { value: 'gregory', label: systemLabels.gregory },
1577
+ { value: 'hijri', label: systemLabels.hijri },
1578
+ ]}
1579
+ selected={dualTab}
1580
+ onSelect={setDualTab}
1581
+ accessibilityLabel={systemA11yLabel}
1582
+ tokens={tokens}
1583
+ styles={styles}
1584
+ testID={testID ? `${testID}-dual-tabs` : undefined}
1585
+ />
1586
+ );
1587
+ // The Hijri tab lays out its calendar and time rows right to left; the
1588
+ // wrapper always renders, so a tab change doesn't remount the time boxes.
1589
+ const hijriShown = shownSystem === 'hijri';
1590
+ const mirrored = (node: React.ReactNode) =>
1591
+ node == null ? null : (
1592
+ <RtlContext.Provider value={hijriShown || null}>
1593
+ <View style={hijriShown ? RTL : undefined}>{node}</View>
1594
+ </RtlContext.Provider>
1595
+ );
1596
+ // Another calendar opens on the days the last one showed, else on the
1597
+ // selection (shown or not), else where the picker would open.
1598
+ const calendarAnchor =
1599
+ shownAnchor.current ??
1600
+ (selectionShown ? null : firstSelectedDay(selection)) ??
1601
+ initialVisibleDate;
1602
+
1603
+ const calendar = (
1604
+ <View style={folded ? FOLDED : undefined}>
1605
+ {calendarSwitchNode}
1606
+ {dualTabs}
1607
+ {mirrored(
1608
+ <Calendar
1609
+ key={shownSystem}
1610
+ constraints={selectionShown ? calendarConstraints : constraints}
1611
+ today={today}
1612
+ initialVisibleDate={calendarAnchor}
1613
+ selection={selectionShown ? selection : emptySelection(selection)}
1614
+ onSelectDay={onSelectDayStable}
1615
+ firstDayOfWeek={firstDayOfWeek}
1616
+ showOutsideDays={showOutsideDays}
1617
+ showHolidays={showHolidays}
1618
+ yearRange={yearRange}
1619
+ locale={shownSystem === 'hijri' ? hijriLocale : loc}
1620
+ calendarSystem={shownSystem}
1621
+ initialView={initialView}
1622
+ precision={precision}
1623
+ enableMonthSwipe={enableMonthSwipe}
1624
+ showToday={showToday}
1625
+ onVisibleMonthChange={handleVisibleMonth}
1626
+ onAnchorChange={rememberAnchor}
1627
+ renderDay={renderDay}
1628
+ renderWeekday={renderWeekday}
1629
+ renderMonthChip={renderMonthChip}
1630
+ renderYearCell={renderYearCell}
1631
+ renderMonthCell={renderMonthCell}
1632
+ renderEmptyState={renderEmptyState}
1633
+ icons={icons}
1634
+ iconProps={iconProps}
1635
+ tokens={tokens}
1636
+ styles={styles}
1637
+ strings={stringsFor(shownSystem)}
1638
+ testID={testID}
1639
+ />
1640
+ )}
1641
+ </View>
1642
+ );
1152
1643
 
1153
1644
  const timeRow =
1154
1645
  type !== 'datetime' ? null : renderTimeRow ? (
@@ -1179,7 +1670,7 @@ const DateTimePicker: React.FC<DateTimePickerProps> = (props) => {
1179
1670
  onPendingChange={onRowPending}
1180
1671
  tokens={tokens}
1181
1672
  styles={styles}
1182
- strings={strings}
1673
+ strings={stringsFor(shownSystem)}
1183
1674
  testID={testID}
1184
1675
  />
1185
1676
  );
@@ -1192,11 +1683,19 @@ const DateTimePicker: React.FC<DateTimePickerProps> = (props) => {
1192
1683
  ? validation.message
1193
1684
  : null;
1194
1685
 
1686
+ // The field writes the date in the digits of the calendar it was picked in;
1687
+ // the calendar and the time rows follow the tab that shows.
1195
1688
  const sheetContent = (
1196
1689
  <>
1197
- {sheetField}
1198
- {calendar}
1199
- {timeRow}
1690
+ <NumeralsContext.Provider value={digitsFor(draftSystem)}>
1691
+ {sheetField}
1692
+ </NumeralsContext.Provider>
1693
+ <NumeralsContext.Provider value={digitsFor(shownSystem)}>
1694
+ {calendar}
1695
+ </NumeralsContext.Provider>
1696
+ <NumeralsContext.Provider value={digitsFor(shownSystem)}>
1697
+ {mirrored(timeRow)}
1698
+ </NumeralsContext.Provider>
1200
1699
  {validationMessage ? (
1201
1700
  <FieldMessages
1202
1701
  error={validationMessage}
@@ -1212,7 +1711,7 @@ const DateTimePicker: React.FC<DateTimePickerProps> = (props) => {
1212
1711
  );
1213
1712
 
1214
1713
  return (
1215
- <>
1714
+ <NumeralsContext.Provider value={digitsFor(savedSystem)}>
1216
1715
  {trigger}
1217
1716
  <PickerContainer
1218
1717
  presentation={presentation}
@@ -1235,7 +1734,7 @@ const DateTimePicker: React.FC<DateTimePickerProps> = (props) => {
1235
1734
  >
1236
1735
  {sheetContent}
1237
1736
  </PickerContainer>
1238
- </>
1737
+ </NumeralsContext.Provider>
1239
1738
  );
1240
1739
  };
1241
1740