rn-grid-date-time-picker 1.1.0 → 1.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (163) hide show
  1. package/CHANGELOG.md +44 -0
  2. package/lib/commonjs/components/DateTimePicker.js +55 -13
  3. package/lib/commonjs/components/DateTimePicker.js.map +1 -1
  4. package/lib/commonjs/components/calendar/CalendarHeader.js +3 -1
  5. package/lib/commonjs/components/calendar/CalendarHeader.js.map +1 -1
  6. package/lib/commonjs/components/calendar/DayGrid.js +4 -1
  7. package/lib/commonjs/components/calendar/DayGrid.js.map +1 -1
  8. package/lib/commonjs/components/calendar/calendarStyles.js +2 -1
  9. package/lib/commonjs/components/calendar/calendarStyles.js.map +1 -1
  10. package/lib/commonjs/components/field/RangeFields.js +2 -0
  11. package/lib/commonjs/components/field/RangeFields.js.map +1 -1
  12. package/lib/commonjs/components/field/SegmentedInput.js +1 -1
  13. package/lib/commonjs/components/field/SegmentedInput.js.map +1 -1
  14. package/lib/commonjs/components/field/ValueField.js +57 -5
  15. package/lib/commonjs/components/field/ValueField.js.map +1 -1
  16. package/lib/commonjs/components/field/fieldStyles.js +17 -0
  17. package/lib/commonjs/components/field/fieldStyles.js.map +1 -1
  18. package/lib/commonjs/components/field/fieldText.js +12 -3
  19. package/lib/commonjs/components/field/fieldText.js.map +1 -1
  20. package/lib/commonjs/components/theme/defaultStrings.js +6 -2
  21. package/lib/commonjs/components/theme/defaultStrings.js.map +1 -1
  22. package/lib/commonjs/components/theme/direction.js +17 -0
  23. package/lib/commonjs/components/theme/direction.js.map +1 -0
  24. package/lib/commonjs/components/theme/styleKeys.js +9 -2
  25. package/lib/commonjs/components/theme/styleKeys.js.map +1 -1
  26. package/lib/commonjs/components/time/TimeRow.js +42 -4
  27. package/lib/commonjs/components/time/TimeRow.js.map +1 -1
  28. package/lib/commonjs/components/time/TimeRows.js +27 -1
  29. package/lib/commonjs/components/time/TimeRows.js.map +1 -1
  30. package/lib/commonjs/components/time/timeStyles.js +19 -6
  31. package/lib/commonjs/components/time/timeStyles.js.map +1 -1
  32. package/lib/commonjs/components/types.js +2 -0
  33. package/lib/commonjs/components/types.js.map +1 -1
  34. package/lib/commonjs/core/calendarSystem.js +39 -1
  35. package/lib/commonjs/core/calendarSystem.js.map +1 -1
  36. package/lib/commonjs/core/zoned.js +18 -10
  37. package/lib/commonjs/core/zoned.js.map +1 -1
  38. package/lib/commonjs/index.js +6 -0
  39. package/lib/commonjs/index.js.map +1 -1
  40. package/lib/module/components/DateTimePicker.js +57 -15
  41. package/lib/module/components/DateTimePicker.js.map +1 -1
  42. package/lib/module/components/calendar/CalendarHeader.js +4 -2
  43. package/lib/module/components/calendar/CalendarHeader.js.map +1 -1
  44. package/lib/module/components/calendar/DayGrid.js +5 -2
  45. package/lib/module/components/calendar/DayGrid.js.map +1 -1
  46. package/lib/module/components/calendar/calendarStyles.js +2 -1
  47. package/lib/module/components/calendar/calendarStyles.js.map +1 -1
  48. package/lib/module/components/field/RangeFields.js +2 -0
  49. package/lib/module/components/field/RangeFields.js.map +1 -1
  50. package/lib/module/components/field/SegmentedInput.js +1 -1
  51. package/lib/module/components/field/SegmentedInput.js.map +1 -1
  52. package/lib/module/components/field/ValueField.js +59 -7
  53. package/lib/module/components/field/ValueField.js.map +1 -1
  54. package/lib/module/components/field/fieldStyles.js +17 -0
  55. package/lib/module/components/field/fieldStyles.js.map +1 -1
  56. package/lib/module/components/field/fieldText.js +10 -2
  57. package/lib/module/components/field/fieldText.js.map +1 -1
  58. package/lib/module/components/theme/defaultStrings.js +4 -1
  59. package/lib/module/components/theme/defaultStrings.js.map +1 -1
  60. package/lib/module/components/theme/direction.js +13 -0
  61. package/lib/module/components/theme/direction.js.map +1 -0
  62. package/lib/module/components/theme/styleKeys.js +9 -2
  63. package/lib/module/components/theme/styleKeys.js.map +1 -1
  64. package/lib/module/components/time/TimeRow.js +43 -5
  65. package/lib/module/components/time/TimeRow.js.map +1 -1
  66. package/lib/module/components/time/TimeRows.js +27 -1
  67. package/lib/module/components/time/TimeRows.js.map +1 -1
  68. package/lib/module/components/time/timeStyles.js +19 -6
  69. package/lib/module/components/time/timeStyles.js.map +1 -1
  70. package/lib/module/components/types.js +2 -0
  71. package/lib/module/components/types.js.map +1 -1
  72. package/lib/module/core/calendarSystem.js +37 -0
  73. package/lib/module/core/calendarSystem.js.map +1 -1
  74. package/lib/module/core/zoned.js +18 -10
  75. package/lib/module/core/zoned.js.map +1 -1
  76. package/lib/module/index.js +1 -1
  77. package/lib/module/index.js.map +1 -1
  78. package/lib/typescript/commonjs/src/components/DateTimePicker.d.ts.map +1 -1
  79. package/lib/typescript/commonjs/src/components/calendar/CalendarHeader.d.ts.map +1 -1
  80. package/lib/typescript/commonjs/src/components/calendar/DayGrid.d.ts.map +1 -1
  81. package/lib/typescript/commonjs/src/components/calendar/calendarStyles.d.ts +1 -1
  82. package/lib/typescript/commonjs/src/components/calendar/calendarStyles.d.ts.map +1 -1
  83. package/lib/typescript/commonjs/src/components/field/RangeFields.d.ts +2 -0
  84. package/lib/typescript/commonjs/src/components/field/RangeFields.d.ts.map +1 -1
  85. package/lib/typescript/commonjs/src/components/field/ValueField.d.ts +4 -0
  86. package/lib/typescript/commonjs/src/components/field/ValueField.d.ts.map +1 -1
  87. package/lib/typescript/commonjs/src/components/field/fieldStyles.d.ts +15 -0
  88. package/lib/typescript/commonjs/src/components/field/fieldStyles.d.ts.map +1 -1
  89. package/lib/typescript/commonjs/src/components/field/fieldText.d.ts +5 -0
  90. package/lib/typescript/commonjs/src/components/field/fieldText.d.ts.map +1 -1
  91. package/lib/typescript/commonjs/src/components/theme/defaultStrings.d.ts +2 -0
  92. package/lib/typescript/commonjs/src/components/theme/defaultStrings.d.ts.map +1 -1
  93. package/lib/typescript/commonjs/src/components/theme/direction.d.ts +8 -0
  94. package/lib/typescript/commonjs/src/components/theme/direction.d.ts.map +1 -0
  95. package/lib/typescript/commonjs/src/components/theme/styleKeys.d.ts +1 -1
  96. package/lib/typescript/commonjs/src/components/theme/styleKeys.d.ts.map +1 -1
  97. package/lib/typescript/commonjs/src/components/time/TimeRow.d.ts +4 -0
  98. package/lib/typescript/commonjs/src/components/time/TimeRow.d.ts.map +1 -1
  99. package/lib/typescript/commonjs/src/components/time/TimeRows.d.ts +7 -0
  100. package/lib/typescript/commonjs/src/components/time/TimeRows.d.ts.map +1 -1
  101. package/lib/typescript/commonjs/src/components/time/timeStyles.d.ts +24 -3
  102. package/lib/typescript/commonjs/src/components/time/timeStyles.d.ts.map +1 -1
  103. package/lib/typescript/commonjs/src/components/types.d.ts +28 -0
  104. package/lib/typescript/commonjs/src/components/types.d.ts.map +1 -1
  105. package/lib/typescript/commonjs/src/core/calendarSystem.d.ts +10 -0
  106. package/lib/typescript/commonjs/src/core/calendarSystem.d.ts.map +1 -1
  107. package/lib/typescript/commonjs/src/core/zoned.d.ts +5 -1
  108. package/lib/typescript/commonjs/src/core/zoned.d.ts.map +1 -1
  109. package/lib/typescript/commonjs/src/index.d.ts +3 -3
  110. package/lib/typescript/commonjs/src/index.d.ts.map +1 -1
  111. package/lib/typescript/module/src/components/DateTimePicker.d.ts.map +1 -1
  112. package/lib/typescript/module/src/components/calendar/CalendarHeader.d.ts.map +1 -1
  113. package/lib/typescript/module/src/components/calendar/DayGrid.d.ts.map +1 -1
  114. package/lib/typescript/module/src/components/calendar/calendarStyles.d.ts +1 -1
  115. package/lib/typescript/module/src/components/calendar/calendarStyles.d.ts.map +1 -1
  116. package/lib/typescript/module/src/components/field/RangeFields.d.ts +2 -0
  117. package/lib/typescript/module/src/components/field/RangeFields.d.ts.map +1 -1
  118. package/lib/typescript/module/src/components/field/ValueField.d.ts +4 -0
  119. package/lib/typescript/module/src/components/field/ValueField.d.ts.map +1 -1
  120. package/lib/typescript/module/src/components/field/fieldStyles.d.ts +15 -0
  121. package/lib/typescript/module/src/components/field/fieldStyles.d.ts.map +1 -1
  122. package/lib/typescript/module/src/components/field/fieldText.d.ts +5 -0
  123. package/lib/typescript/module/src/components/field/fieldText.d.ts.map +1 -1
  124. package/lib/typescript/module/src/components/theme/defaultStrings.d.ts +2 -0
  125. package/lib/typescript/module/src/components/theme/defaultStrings.d.ts.map +1 -1
  126. package/lib/typescript/module/src/components/theme/direction.d.ts +8 -0
  127. package/lib/typescript/module/src/components/theme/direction.d.ts.map +1 -0
  128. package/lib/typescript/module/src/components/theme/styleKeys.d.ts +1 -1
  129. package/lib/typescript/module/src/components/theme/styleKeys.d.ts.map +1 -1
  130. package/lib/typescript/module/src/components/time/TimeRow.d.ts +4 -0
  131. package/lib/typescript/module/src/components/time/TimeRow.d.ts.map +1 -1
  132. package/lib/typescript/module/src/components/time/TimeRows.d.ts +7 -0
  133. package/lib/typescript/module/src/components/time/TimeRows.d.ts.map +1 -1
  134. package/lib/typescript/module/src/components/time/timeStyles.d.ts +24 -3
  135. package/lib/typescript/module/src/components/time/timeStyles.d.ts.map +1 -1
  136. package/lib/typescript/module/src/components/types.d.ts +28 -0
  137. package/lib/typescript/module/src/components/types.d.ts.map +1 -1
  138. package/lib/typescript/module/src/core/calendarSystem.d.ts +10 -0
  139. package/lib/typescript/module/src/core/calendarSystem.d.ts.map +1 -1
  140. package/lib/typescript/module/src/core/zoned.d.ts +5 -1
  141. package/lib/typescript/module/src/core/zoned.d.ts.map +1 -1
  142. package/lib/typescript/module/src/index.d.ts +3 -3
  143. package/lib/typescript/module/src/index.d.ts.map +1 -1
  144. package/package.json +1 -1
  145. package/src/components/DateTimePicker.tsx +97 -47
  146. package/src/components/calendar/CalendarHeader.tsx +3 -2
  147. package/src/components/calendar/DayGrid.tsx +5 -2
  148. package/src/components/calendar/calendarStyles.ts +2 -1
  149. package/src/components/field/RangeFields.tsx +4 -0
  150. package/src/components/field/SegmentedInput.tsx +1 -1
  151. package/src/components/field/ValueField.tsx +83 -15
  152. package/src/components/field/fieldStyles.ts +6 -0
  153. package/src/components/field/fieldText.ts +25 -4
  154. package/src/components/theme/defaultStrings.ts +11 -1
  155. package/src/components/theme/direction.ts +12 -0
  156. package/src/components/theme/styleKeys.ts +9 -0
  157. package/src/components/time/TimeRow.tsx +49 -2
  158. package/src/components/time/TimeRows.tsx +38 -2
  159. package/src/components/time/timeStyles.ts +12 -4
  160. package/src/components/types.ts +29 -0
  161. package/src/core/calendarSystem.ts +42 -0
  162. package/src/core/zoned.ts +15 -3
  163. package/src/index.tsx +3 -2
@@ -28,6 +28,7 @@ import {
28
28
  } from '../core/draft';
29
29
  import {
30
30
  hijriDateText,
31
+ hijriPeriods,
31
32
  isHijriSupported,
32
33
  resolveCalendarSystem,
33
34
  type CalendarSystem,
@@ -53,6 +54,7 @@ import type {
53
54
  import Calendar from './calendar/Calendar';
54
55
  import CalendarSwitch from './calendar/CalendarSwitch';
55
56
  import { localeNames } from './calendar/localeNames';
57
+ import { RtlContext } from './theme/direction';
56
58
  import { NumeralsContext } from './theme/numerals';
57
59
  import { firstSelectedDay, type CalendarSelection } from './calendar/selection';
58
60
  import PickerContainer from './container/PickerContainer';
@@ -71,7 +73,11 @@ import { useOpenPicker } from './hooks/useOpenPicker';
71
73
  import { usePickerDraft } from './hooks/usePickerDraft';
72
74
  import { useTodayTick } from './hooks/useTodayTick';
73
75
  import TimeRows, { type PendingTimes } from './time/TimeRows';
74
- import { DEFAULT_STRINGS, resolveStrings } from './theme/defaultStrings';
76
+ import {
77
+ DEFAULT_STRINGS,
78
+ compactStrings,
79
+ resolveStrings,
80
+ } from './theme/defaultStrings';
75
81
  import { buildTokens } from './theme/tokens';
76
82
  import {
77
83
  DEFAULT_YEAR_RANGE,
@@ -86,6 +92,7 @@ const sameValue = (a: DateTimeValue, b: DateTimeValue): boolean =>
86
92
  (a.time?.minute ?? -1) === (b.time?.minute ?? -1);
87
93
 
88
94
  const ALL_SYSTEMS: CalendarSystem[] = ['gregory', 'hijri', 'dual'];
95
+ const RTL = { direction: 'rtl' } as const;
89
96
 
90
97
  /** The same kind of selection with nothing in it. */
91
98
  const emptySelection = (sel: CalendarSelection): CalendarSelection =>
@@ -197,6 +204,8 @@ const DateTimePicker: React.FC<DateTimePickerProps> = (props) => {
197
204
  minuteStep: minuteStepProp = 1,
198
205
  timeInput = 'typed',
199
206
  newDayTime,
207
+ timeRowDate = false,
208
+ renderTimeLabel,
200
209
  holidays: holidaysProp,
201
210
  loadHolidays,
202
211
  disableHolidays,
@@ -210,6 +219,7 @@ const DateTimePicker: React.FC<DateTimePickerProps> = (props) => {
210
219
  theme,
211
220
  styles,
212
221
  strings: stringOverrides,
222
+ hijriStrings: hijriOverrides,
213
223
  testID,
214
224
  } = props;
215
225
 
@@ -294,6 +304,20 @@ const DateTimePicker: React.FC<DateTimePickerProps> = (props) => {
294
304
  if (kind === 'latin') return latinDigits;
295
305
  return localeDigits(sys === 'hijri' ? hijriLocale : loc);
296
306
  };
307
+ /**
308
+ * The strings for one calendar. A Hijri one takes `hijriStrings` first, then
309
+ * AM / PM in `hijriLocale` (ص / م), then `strings`.
310
+ */
311
+ const hijriStrings = useMemo(
312
+ () => ({
313
+ ...strings,
314
+ ...hijriPeriods(hijriLocale),
315
+ ...compactStrings(hijriOverrides),
316
+ }),
317
+ [strings, hijriLocale, hijriOverrides]
318
+ );
319
+ const stringsFor = (sys: 'gregory' | 'hijri') =>
320
+ sys === 'hijri' ? hijriStrings : strings;
297
321
  const monthNames = useMemo(() => localeNames(loc).monthsLong, [loc]);
298
322
  /** A month or year pick shown as text: "March 2026" or "2026". */
299
323
  const periodText = (v: DateTimeValue | null): string => {
@@ -1249,6 +1273,11 @@ const DateTimePicker: React.FC<DateTimePickerProps> = (props) => {
1249
1273
  : undefined;
1250
1274
  const savedDateText = dateTextFor(savedSystem);
1251
1275
  const draftDateText = dateTextFor(draftSystem);
1276
+ const savedStrings = stringsFor(savedSystem);
1277
+ const draftStrings = stringsFor(draftSystem);
1278
+ // Error messages inside the picker follow the tab on screen; the on-screen
1279
+ // field's keep `strings`.
1280
+ const shownStrings = stringsFor(shownSystem);
1252
1281
 
1253
1282
  // What the on-screen field shows: the saved value, never the draft.
1254
1283
  const savedSingle = saved.mode === 'single' ? saved.slot.value : null;
@@ -1262,7 +1291,7 @@ const DateTimePicker: React.FC<DateTimePickerProps> = (props) => {
1262
1291
  timeFormat,
1263
1292
  strings.rangeSeparator,
1264
1293
  savedDateText,
1265
- strings
1294
+ savedStrings
1266
1295
  )
1267
1296
  : saved.mode === 'multiple'
1268
1297
  ? formatMultipleText(
@@ -1271,7 +1300,7 @@ const DateTimePicker: React.FC<DateTimePickerProps> = (props) => {
1271
1300
  dateFormat,
1272
1301
  timeFormat,
1273
1302
  savedDateText,
1274
- strings
1303
+ savedStrings
1275
1304
  )
1276
1305
  : precision !== 'day'
1277
1306
  ? periodText(savedSingle)
@@ -1332,7 +1361,7 @@ const DateTimePicker: React.FC<DateTimePickerProps> = (props) => {
1332
1361
  dateFormat,
1333
1362
  timeFormat,
1334
1363
  savedDateText,
1335
- strings
1364
+ savedStrings
1336
1365
  )
1337
1366
  ),
1338
1367
  disabled,
@@ -1360,7 +1389,7 @@ const DateTimePicker: React.FC<DateTimePickerProps> = (props) => {
1360
1389
  draft.draft.mode === 'single' ? draft.draft.slot.value : null;
1361
1390
  // A time outside the limits is explained under the time row, not here.
1362
1391
  const sheetValueError =
1363
- sheetValue && dayOff(sheetValue.date) ? strings.errorNotAllowed : '';
1392
+ sheetValue && dayOff(sheetValue.date) ? shownStrings.errorNotAllowed : '';
1364
1393
 
1365
1394
  let rangeError = '';
1366
1395
  if (d.mode === 'range' && d.from.value && d.to.value) {
@@ -1369,14 +1398,14 @@ const DateTimePicker: React.FC<DateTimePickerProps> = (props) => {
1369
1398
  constraints.dayStatus(d.from.value.date).disabled ||
1370
1399
  constraints.dayStatus(d.to.value.date).disabled
1371
1400
  )
1372
- rangeError = strings.errorNotAllowed;
1401
+ rangeError = shownStrings.errorNotAllowed;
1373
1402
  else if (previewIssue === 'RANGE_ORDER')
1374
- rangeError = strings.errorRangeOrder;
1403
+ rangeError = shownStrings.errorRangeOrder;
1375
1404
  else if (previewIssue === 'RANGE_LENGTH')
1376
1405
  rangeError =
1377
1406
  range?.minDays !== undefined && length < range.minDays
1378
- ? strings.errorRangeTooShort(range.minDays)
1379
- : strings.errorRangeTooLong(range?.maxDays ?? length);
1407
+ ? shownStrings.errorRangeTooShort(range.minDays)
1408
+ : shownStrings.errorRangeTooLong(range?.maxDays ?? length);
1380
1409
  }
1381
1410
 
1382
1411
  const fieldCommon = {
@@ -1398,6 +1427,8 @@ const DateTimePicker: React.FC<DateTimePickerProps> = (props) => {
1398
1427
  ) : (
1399
1428
  <ValueField
1400
1429
  {...fieldCommon}
1430
+ strings={savedStrings}
1431
+ errorStrings={strings}
1401
1432
  value={savedSingle}
1402
1433
  displayText={savedText}
1403
1434
  dateText={savedDateText}
@@ -1442,6 +1473,8 @@ const DateTimePicker: React.FC<DateTimePickerProps> = (props) => {
1442
1473
  ) : mode === 'single' ? (
1443
1474
  <ValueField
1444
1475
  {...fieldCommon}
1476
+ strings={draftStrings}
1477
+ errorStrings={shownStrings}
1445
1478
  value={sheetValue}
1446
1479
  displayText={precision !== 'day' ? periodText(sheetValue) : undefined}
1447
1480
  dateText={draftDateText}
@@ -1459,6 +1492,8 @@ const DateTimePicker: React.FC<DateTimePickerProps> = (props) => {
1459
1492
  ) : d.mode === 'range' ? (
1460
1493
  <RangeFields
1461
1494
  {...fieldCommon}
1495
+ strings={draftStrings}
1496
+ errorStrings={shownStrings}
1462
1497
  layout={rangeLayout}
1463
1498
  dateText={draftDateText}
1464
1499
  showIcon={sheetOptions?.fieldIcon}
@@ -1479,6 +1514,7 @@ const DateTimePicker: React.FC<DateTimePickerProps> = (props) => {
1479
1514
  ) : d.mode === 'multiple' ? (
1480
1515
  <ValueField
1481
1516
  {...fieldCommon}
1517
+ strings={draftStrings}
1482
1518
  value={null}
1483
1519
  displayText={formatMultipleText(
1484
1520
  draftValues,
@@ -1486,7 +1522,7 @@ const DateTimePicker: React.FC<DateTimePickerProps> = (props) => {
1486
1522
  dateFormat,
1487
1523
  timeFormat,
1488
1524
  draftDateText,
1489
- strings
1525
+ draftStrings
1490
1526
  )}
1491
1527
  placeholder={sheetOptions?.fieldPlaceholder}
1492
1528
  allowClear={allowClear}
@@ -1550,6 +1586,15 @@ const DateTimePicker: React.FC<DateTimePickerProps> = (props) => {
1550
1586
  testID={testID ? `${testID}-dual-tabs` : undefined}
1551
1587
  />
1552
1588
  );
1589
+ // The Hijri tab lays out its calendar and time rows right to left; the
1590
+ // wrapper always renders, so a tab change doesn't remount the time boxes.
1591
+ const hijriShown = shownSystem === 'hijri';
1592
+ const mirrored = (node: React.ReactNode) =>
1593
+ node == null ? null : (
1594
+ <RtlContext.Provider value={hijriShown || null}>
1595
+ <View style={hijriShown ? RTL : undefined}>{node}</View>
1596
+ </RtlContext.Provider>
1597
+ );
1553
1598
  // Another calendar opens on the days the last one showed, else on the
1554
1599
  // selection (shown or not), else where the picker would open.
1555
1600
  const calendarAnchor =
@@ -1561,38 +1606,40 @@ const DateTimePicker: React.FC<DateTimePickerProps> = (props) => {
1561
1606
  <View style={folded ? FOLDED : undefined}>
1562
1607
  {calendarSwitchNode}
1563
1608
  {dualTabs}
1564
- <Calendar
1565
- key={shownSystem}
1566
- constraints={selectionShown ? calendarConstraints : constraints}
1567
- today={today}
1568
- initialVisibleDate={calendarAnchor}
1569
- selection={selectionShown ? selection : emptySelection(selection)}
1570
- onSelectDay={onSelectDayStable}
1571
- firstDayOfWeek={firstDayOfWeek}
1572
- showOutsideDays={showOutsideDays}
1573
- showHolidays={showHolidays}
1574
- yearRange={yearRange}
1575
- locale={shownSystem === 'hijri' ? hijriLocale : loc}
1576
- calendarSystem={shownSystem}
1577
- initialView={initialView}
1578
- precision={precision}
1579
- enableMonthSwipe={enableMonthSwipe}
1580
- showToday={showToday}
1581
- onVisibleMonthChange={handleVisibleMonth}
1582
- onAnchorChange={rememberAnchor}
1583
- renderDay={renderDay}
1584
- renderWeekday={renderWeekday}
1585
- renderMonthChip={renderMonthChip}
1586
- renderYearCell={renderYearCell}
1587
- renderMonthCell={renderMonthCell}
1588
- renderEmptyState={renderEmptyState}
1589
- icons={icons}
1590
- iconProps={iconProps}
1591
- tokens={tokens}
1592
- styles={styles}
1593
- strings={strings}
1594
- testID={testID}
1595
- />
1609
+ {mirrored(
1610
+ <Calendar
1611
+ key={shownSystem}
1612
+ constraints={selectionShown ? calendarConstraints : constraints}
1613
+ today={today}
1614
+ initialVisibleDate={calendarAnchor}
1615
+ selection={selectionShown ? selection : emptySelection(selection)}
1616
+ onSelectDay={onSelectDayStable}
1617
+ firstDayOfWeek={firstDayOfWeek}
1618
+ showOutsideDays={showOutsideDays}
1619
+ showHolidays={showHolidays}
1620
+ yearRange={yearRange}
1621
+ locale={shownSystem === 'hijri' ? hijriLocale : loc}
1622
+ calendarSystem={shownSystem}
1623
+ initialView={initialView}
1624
+ precision={precision}
1625
+ enableMonthSwipe={enableMonthSwipe}
1626
+ showToday={showToday}
1627
+ onVisibleMonthChange={handleVisibleMonth}
1628
+ onAnchorChange={rememberAnchor}
1629
+ renderDay={renderDay}
1630
+ renderWeekday={renderWeekday}
1631
+ renderMonthChip={renderMonthChip}
1632
+ renderYearCell={renderYearCell}
1633
+ renderMonthCell={renderMonthCell}
1634
+ renderEmptyState={renderEmptyState}
1635
+ icons={icons}
1636
+ iconProps={iconProps}
1637
+ tokens={tokens}
1638
+ styles={styles}
1639
+ strings={stringsFor(shownSystem)}
1640
+ testID={testID}
1641
+ />
1642
+ )}
1596
1643
  </View>
1597
1644
  );
1598
1645
 
@@ -1623,9 +1670,12 @@ const DateTimePicker: React.FC<DateTimePickerProps> = (props) => {
1623
1670
  onSpinChange={setWheelSpinning}
1624
1671
  onFocusChange={(row, f) => reportFocus(`time-${row}`, f)}
1625
1672
  onPendingChange={onRowPending}
1673
+ timeRowDate={timeRowDate}
1674
+ renderTimeLabel={renderTimeLabel}
1675
+ dateText={draftDateText}
1626
1676
  tokens={tokens}
1627
1677
  styles={styles}
1628
- strings={strings}
1678
+ strings={stringsFor(shownSystem)}
1629
1679
  testID={testID}
1630
1680
  />
1631
1681
  );
@@ -1638,8 +1688,8 @@ const DateTimePicker: React.FC<DateTimePickerProps> = (props) => {
1638
1688
  ? validation.message
1639
1689
  : null;
1640
1690
 
1641
- // The field and the time row write the date in the digits of the calendar
1642
- // it was picked in; the calendar uses its own.
1691
+ // The field writes the date in the digits of the calendar it was picked in;
1692
+ // the calendar and the time rows follow the tab that shows.
1643
1693
  const sheetContent = (
1644
1694
  <>
1645
1695
  <NumeralsContext.Provider value={digitsFor(draftSystem)}>
@@ -1648,8 +1698,8 @@ const DateTimePicker: React.FC<DateTimePickerProps> = (props) => {
1648
1698
  <NumeralsContext.Provider value={digitsFor(shownSystem)}>
1649
1699
  {calendar}
1650
1700
  </NumeralsContext.Provider>
1651
- <NumeralsContext.Provider value={digitsFor(draftSystem)}>
1652
- {timeRow}
1701
+ <NumeralsContext.Provider value={digitsFor(shownSystem)}>
1702
+ {mirrored(timeRow)}
1653
1703
  </NumeralsContext.Provider>
1654
1704
  {validationMessage ? (
1655
1705
  <FieldMessages
@@ -1,6 +1,5 @@
1
1
  import React, { useEffect, useRef } from 'react';
2
2
  import {
3
- I18nManager,
4
3
  Pressable,
5
4
  ScrollView,
6
5
  Text,
@@ -18,6 +17,7 @@ import {
18
17
  import type { PickerStrings } from '../theme/defaultStrings';
19
18
  import type { PickerStyles } from '../theme/styleKeys';
20
19
  import { MAX_FONT_SCALE } from '../theme/a11y';
20
+ import { useIsRTL } from '../theme/direction';
21
21
  import { useNumerals } from '../theme/numerals';
22
22
  import type { ThemeTokens } from '../theme/tokens';
23
23
  import { useReduceMotion } from '../hooks/useReduceMotion';
@@ -60,6 +60,7 @@ export const CalendarHeader: React.FC<HeaderProps> = ({
60
60
  testID,
61
61
  }) => {
62
62
  const digits = useNumerals();
63
+ const isRTL = useIsRTL();
63
64
  const s = buildCalendarStyles(tokens);
64
65
  const ico = resolveIcons(icons);
65
66
  const arrow = (
@@ -69,7 +70,7 @@ export const CalendarHeader: React.FC<HeaderProps> = ({
69
70
  label: string
70
71
  ) => {
71
72
  // Chevrons point in the reading direction; mirrored in right-to-left layouts.
72
- const pointsLeft = (dir === 'prev') !== I18nManager.isRTL;
73
+ const pointsLeft = (dir === 'prev') !== isRTL;
73
74
  const chevron = pointsLeft ? 'chevronLeft' : 'chevronRight';
74
75
  return (
75
76
  <Pressable
@@ -1,5 +1,5 @@
1
1
  import React, { useMemo, useRef } from 'react';
2
- import { I18nManager, PanResponder, Text, View } from 'react-native';
2
+ import { PanResponder, Text, View } from 'react-native';
3
3
  import type { Constraints } from '../../core/constraints';
4
4
  import { isSameDay } from '../../core/calendarDate';
5
5
  import { buildMonthGrid, weekdayOrder } from '../../core/monthGrid';
@@ -7,6 +7,7 @@ import type { CalendarDate, Weekday } from '../../core/types';
7
7
  import type { PickerStrings } from '../theme/defaultStrings';
8
8
  import type { PickerStyles } from '../theme/styleKeys';
9
9
  import { MAX_FONT_SCALE } from '../theme/a11y';
10
+ import { useIsRTL } from '../theme/direction';
10
11
  import type { ThemeTokens } from '../theme/tokens';
11
12
  import { buildCalendarStyles } from './calendarStyles';
12
13
  import DayCell, { type DayCellState } from './DayCell';
@@ -84,6 +85,8 @@ const DayGrid: React.FC<DayGridProps> = ({
84
85
 
85
86
  const onSwipeRef = useRef(onSwipe);
86
87
  onSwipeRef.current = onSwipe;
88
+ const isRTLRef = useRef(false);
89
+ isRTLRef.current = useIsRTL();
87
90
  const pan = useMemo(
88
91
  () =>
89
92
  PanResponder.create({
@@ -97,7 +100,7 @@ const DayGrid: React.FC<DayGridProps> = ({
97
100
  // on the grid (PanResponder blocks native views by default).
98
101
  onShouldBlockNativeResponder: () => false,
99
102
  onPanResponderRelease: (_, g) => {
100
- const step = swipeStep(g.dx, I18nManager.isRTL);
103
+ const step = swipeStep(g.dx, isRTLRef.current);
101
104
  if (step !== 0) onSwipeRef.current?.(step);
102
105
  },
103
106
  }),
@@ -103,7 +103,8 @@ const create = (t: ThemeTokens) =>
103
103
  disabledText: { color: t.disabledText },
104
104
  holidayText: { color: t.holiday },
105
105
  pressed: { backgroundColor: t.rangeFill },
106
- grid: { flexDirection: 'row', flexWrap: 'wrap', paddingVertical: 8 },
106
+ // No top padding: the header's margin and the cell padding already space the first row.
107
+ grid: { flexDirection: 'row', flexWrap: 'wrap', paddingBottom: 8 },
107
108
  gridCell: { width: '33.33%', padding: 6 },
108
109
  gridButton: {
109
110
  minHeight: t.touchTarget,
@@ -47,6 +47,8 @@ interface Props {
47
47
  tokens: ThemeTokens;
48
48
  styles?: PickerStyles;
49
49
  strings: PickerStrings;
50
+ /** The texts of the typed-input errors. Default `strings`. */
51
+ errorStrings?: PickerStrings;
50
52
  testID?: string;
51
53
  }
52
54
 
@@ -84,6 +86,7 @@ const RangeFields: React.FC<Props> = (props) => {
84
86
  tokens,
85
87
  styles,
86
88
  strings,
89
+ errorStrings,
87
90
  testID,
88
91
  } = props;
89
92
  const common = {
@@ -98,6 +101,7 @@ const RangeFields: React.FC<Props> = (props) => {
98
101
  tokens,
99
102
  styles,
100
103
  strings,
104
+ errorStrings,
101
105
  disabled,
102
106
  };
103
107
 
@@ -292,7 +292,7 @@ const SegmentedInput: React.FC<Props> = ({
292
292
  {dateSpecs.map((spec, i) => renderNumeric(spec, i))}
293
293
  </View>
294
294
  {timeSpecs.length ? (
295
- <View style={s.segmentGroup}>
295
+ <View style={[s.segmentGroup, dateSpecs.length > 0 && s.segmentTime]}>
296
296
  {timeSpecs.map((spec, i) =>
297
297
  spec.key === 'period' ? renderPeriod() : renderNumeric(spec, i)
298
298
  )}
@@ -1,5 +1,5 @@
1
1
  import React, { useEffect, useRef, useState } from 'react';
2
- import { Pressable, Text, View } from 'react-native';
2
+ import { Pressable, ScrollView, Text, View } from 'react-native';
3
3
  import type { SegmentEditorAction } from '../../core/segmentEditor';
4
4
  import type { ParsedSegments } from '../../core/segments';
5
5
  import type { DateTimeValue, PickerType, TimeFormat } from '../../core/types';
@@ -11,6 +11,7 @@ import {
11
11
  type IconSet,
12
12
  } from '../../icons';
13
13
  import { useTypedSegments } from '../hooks/useTypedSegments';
14
+ import type { ScrollViewRef } from '../nativeTypes';
14
15
  import type { PickerStrings } from '../theme/defaultStrings';
15
16
  import type { PickerStyles } from '../theme/styleKeys';
16
17
  import { useNumerals } from '../theme/numerals';
@@ -18,6 +19,7 @@ import type { ThemeTokens } from '../theme/tokens';
18
19
  import type { InstructionRenderProps, LabelRenderProps } from '../types';
19
20
  import FieldMessages from './FieldMessages';
20
21
  import {
22
+ formatValueParts,
21
23
  formatValueText,
22
24
  placeholderText,
23
25
  segmentErrorText,
@@ -26,6 +28,16 @@ import {
26
28
  import { buildFieldStyles } from './fieldStyles';
27
29
  import SegmentedInput from './SegmentedInput';
28
30
 
31
+ // Hebrew and Arabic letters; Arabic-Indic digits (٠–٩, ۰–۹) are left out, since
32
+ // digits don't set the reading direction.
33
+ const RTL_LETTER = '֐-׿؀-ٟ٪-ۯۺ-ࣿ';
34
+ const FIRST_LETTER_RTL = new RegExp(
35
+ `^[^A-Za-zÀ-ɏ${RTL_LETTER}]*[${RTL_LETTER}]`
36
+ );
37
+ /** Whether a text reads right to left: its first letter is Hebrew or Arabic. */
38
+ export const startsRightToLeft = (text: string): boolean =>
39
+ FIRST_LETTER_RTL.test(text);
40
+
29
41
  export interface ValueFieldProps {
30
42
  type: PickerType;
31
43
  /** The value to show (or to start typing from). */
@@ -79,6 +91,8 @@ export interface ValueFieldProps {
79
91
  tokens: ThemeTokens;
80
92
  styles?: PickerStyles;
81
93
  strings: PickerStrings;
94
+ /** The texts of its typed-input errors. Default `strings`. */
95
+ errorStrings?: PickerStrings;
82
96
  testID?: string;
83
97
  }
84
98
 
@@ -160,10 +174,12 @@ const ValueField: React.FC<ValueFieldProps> = (props) => {
160
174
  tokens,
161
175
  styles,
162
176
  strings,
177
+ errorStrings = strings,
163
178
  testID,
164
179
  } = props;
165
180
  const s = buildFieldStyles(tokens);
166
181
  const ico = resolveIcons(iconOverrides);
182
+ const scrollRef = useRef<ScrollViewRef>(null);
167
183
  const [lastParsed, setLastParsed] = useState<ParsedSegments | null>(null);
168
184
  const { state, dispatch, getState } = useTypedSegments(
169
185
  value,
@@ -205,13 +221,25 @@ const ValueField: React.FC<ValueFieldProps> = (props) => {
205
221
  const typing = !!allowTyping && editable;
206
222
  const typedError =
207
223
  typing && !state.focused && lastParsed && !lastParsed.isEmpty
208
- ? segmentErrorText(lastParsed.errors, strings, yearRange)
224
+ ? segmentErrorText(lastParsed.errors, errorStrings, yearRange)
209
225
  : '';
210
226
  const error = typedError || errorMessage || '';
211
227
  const hasValue = typing ? !state.parsed.isEmpty : !!value || !!displayText;
212
228
  const text =
213
229
  displayText ??
214
230
  formatValueText(value, type, dateFormat, timeFormat, dateText, strings);
231
+ // With a fieldDateText style, a single date-time shows its date and time
232
+ // apart (no sideways scroll).
233
+ const valueParts =
234
+ styles?.fieldDateText && displayText === undefined && value
235
+ ? formatValueParts(value, type, dateFormat, timeFormat, dateText, strings)
236
+ : null;
237
+ const parts = valueParts?.time ? valueParts : null;
238
+ const valueStyle = [
239
+ s.text,
240
+ styles?.fieldText,
241
+ disabled && [s.disabledText, styles?.fieldTextDisabled],
242
+ ];
215
243
 
216
244
  const clear = () => {
217
245
  dispatch({ type: 'reset', value: null });
@@ -300,19 +328,59 @@ const ValueField: React.FC<ValueFieldProps> = (props) => {
300
328
  onPress={onPress}
301
329
  style={fieldStyle}
302
330
  >
303
- <Text
304
- numberOfLines={1}
305
- style={[
306
- s.text,
307
- styles?.fieldText,
308
- !text && [s.placeholder, styles?.fieldPlaceholder],
309
- disabled && [s.disabledText, text ? styles?.fieldTextDisabled : null],
310
- ]}
311
- >
312
- {digits(text) ||
313
- placeholder ||
314
- placeholderText(type, dateFormat, timeFormat, strings)}
315
- </Text>
331
+ {parts ? (
332
+ <>
333
+ <Text
334
+ numberOfLines={1}
335
+ ellipsizeMode="clip"
336
+ style={[valueStyle, s.textScrolled, styles?.fieldDateText]}
337
+ >
338
+ {digits(`${parts.date}${strings.dateTimeSeparator ?? ' '}`)}
339
+ </Text>
340
+ <Text numberOfLines={1} ellipsizeMode="clip" style={valueStyle}>
341
+ {digits(parts.time)}
342
+ </Text>
343
+ </>
344
+ ) : text ? (
345
+ // A value too long for the box scrolls sideways; a tap still opens.
346
+ <ScrollView
347
+ ref={scrollRef}
348
+ horizontal
349
+ showsHorizontalScrollIndicator={false}
350
+ keyboardShouldPersistTaps="always"
351
+ style={s.textScroll}
352
+ contentContainerStyle={s.textScrollContent}
353
+ // Right-to-left text (a Hijri date) starts at its right end.
354
+ onContentSizeChange={() => {
355
+ if (startsRightToLeft(text))
356
+ scrollRef.current?.scrollToEnd({ animated: false });
357
+ }}
358
+ >
359
+ <Text
360
+ numberOfLines={1}
361
+ // Android can measure a line a pixel short (right-to-left text
362
+ // most often) and would then swap its last word for "…".
363
+ ellipsizeMode="clip"
364
+ style={[valueStyle, s.textScrolled]}
365
+ >
366
+ {digits(text)}
367
+ </Text>
368
+ </ScrollView>
369
+ ) : (
370
+ <Text
371
+ numberOfLines={1}
372
+ style={[
373
+ s.text,
374
+ styles?.fieldText,
375
+ s.placeholder,
376
+ styles?.fieldPlaceholder,
377
+ disabled && s.disabledText,
378
+ ]}
379
+ >
380
+ {placeholder ||
381
+ placeholderText(type, dateFormat, timeFormat, strings)}
382
+ </Text>
383
+ )}
316
384
  {icons}
317
385
  </Pressable>
318
386
  );
@@ -32,6 +32,10 @@ const create = (t: ThemeTokens) =>
32
32
  fontSize: t.fontSizeMedium,
33
33
  color: t.text,
34
34
  },
35
+ textScroll: { flex: 1 },
36
+ textScrollContent: { flexGrow: 1, alignItems: 'center' },
37
+ // In the scroll, or as a date set apart from its time, the text keeps its full width.
38
+ textScrolled: { flex: 0, flexShrink: 0 },
35
39
  placeholder: { color: t.placeholder },
36
40
  disabledText: { color: t.disabledText },
37
41
  icons: { flexDirection: 'row', alignItems: 'center', marginStart: 8 },
@@ -55,6 +59,8 @@ const create = (t: ThemeTokens) =>
55
59
  alignItems: 'center',
56
60
  flexWrap: 'nowrap',
57
61
  },
62
+ // After a date, the time sits in the middle of the space left before the icons.
63
+ segmentTime: { flexGrow: 1, justifyContent: 'center' },
58
64
  segment: {
59
65
  paddingVertical: 8,
60
66
  paddingHorizontal: 0,
@@ -36,6 +36,22 @@ export const formatTimeText = (
36
36
  /** Writes a value's date some other way, e.g. as a Hijri date. */
37
37
  export type DateTextFormatter = (value: DateTimeValue) => string;
38
38
 
39
+ /** A value's date and time as two texts; the time is '' for a date or a value without one. */
40
+ export const formatValueParts = (
41
+ value: DateTimeValue,
42
+ type: PickerType,
43
+ dateFormat: string,
44
+ timeFormat: TimeFormat,
45
+ dateText?: DateTextFormatter,
46
+ periods: PeriodLabels = DEFAULT_STRINGS
47
+ ): { date: string; time: string } => ({
48
+ date: dateText ? dateText(value) : formatDateText(value, dateFormat),
49
+ time:
50
+ type === 'datetime' && value.time
51
+ ? formatTimeText(value, timeFormat, periods)
52
+ : '',
53
+ });
54
+
39
55
  export const formatValueText = (
40
56
  value: DateTimeValue | null,
41
57
  type: PickerType,
@@ -45,10 +61,15 @@ export const formatValueText = (
45
61
  periods: PeriodLabels = DEFAULT_STRINGS
46
62
  ): string => {
47
63
  if (!value) return '';
48
- const date = dateText ? dateText(value) : formatDateText(value, dateFormat);
49
- return type === 'datetime' && value.time
50
- ? `${date}${periods.dateTimeSeparator ?? ' '}${formatTimeText(value, timeFormat, periods)}`
51
- : date;
64
+ const { date, time } = formatValueParts(
65
+ value,
66
+ type,
67
+ dateFormat,
68
+ timeFormat,
69
+ dateText,
70
+ periods
71
+ );
72
+ return time ? `${date}${periods.dateTimeSeparator ?? ' '}${time}` : date;
52
73
  };
53
74
 
54
75
  /** '21-10-2026 – 25-10-2026'; just the start (with the dash) while the end is open. */
@@ -124,9 +124,19 @@ export const DEFAULT_STRINGS: Required<PickerStrings> = {
124
124
  a11yRangeEnd: 'end of range',
125
125
  };
126
126
 
127
+ /** The keys that have a value: one left `undefined` (or `null`) keeps what's under it. */
128
+ export const compactStrings = (
129
+ overrides?: Partial<PickerStrings>
130
+ ): Partial<PickerStrings> =>
131
+ overrides
132
+ ? (Object.fromEntries(
133
+ Object.entries(overrides).filter(([, v]) => v != null)
134
+ ) as Partial<PickerStrings>)
135
+ : {};
136
+
127
137
  /** The English defaults with the caller's overrides on top. */
128
138
  export const resolveStrings = (
129
139
  overrides?: Partial<PickerStrings>
130
140
  ): Required<PickerStrings> => {
131
- return { ...DEFAULT_STRINGS, ...overrides };
141
+ return { ...DEFAULT_STRINGS, ...compactStrings(overrides) };
132
142
  };
@@ -0,0 +1,12 @@
1
+ import { createContext, useContext } from 'react';
2
+ import { I18nManager } from 'react-native';
3
+
4
+ /**
5
+ * Whether a part of the picker is laid out right to left. The picker sets it
6
+ * for the calendar and the time rows while the Hijri tab shows; elsewhere the
7
+ * app's direction (`I18nManager.isRTL`) decides.
8
+ */
9
+ export const RtlContext = createContext<boolean | null>(null);
10
+
11
+ export const useIsRTL = (): boolean =>
12
+ useContext(RtlContext) ?? I18nManager.isRTL;