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
@@ -25,6 +25,11 @@ export const STYLE_KEYS = [
25
25
  'fieldText',
26
26
  /** The value's text while the field is disabled (the placeholder keeps its own colour). */
27
27
  'fieldTextDisabled',
28
+ /**
29
+ * Sets the date of a single date-time value apart from its time, e.g.
30
+ * `{ minWidth: '50%' }` starts the time at the middle of the field.
31
+ */
32
+ 'fieldDateText',
28
33
  'fieldPlaceholder',
29
34
  'fieldSegment',
30
35
  'fieldSegmentFocused',
@@ -105,6 +110,10 @@ export const STYLE_KEYS = [
105
110
  // time
106
111
  'timeRow',
107
112
  'timeLabel',
113
+ /** Holds the label and its date (`timeRowDate`) or a `renderTimeLabel` label. */
114
+ 'timeLabelWrap',
115
+ /** The picked day under a time row's label (`timeRowDate`). */
116
+ 'timeLabelDate',
108
117
  'timeBox',
109
118
  'timeBoxFocused',
110
119
  'timeBoxError',
@@ -57,6 +57,10 @@ export interface TimeRowProps {
57
57
  /** The day's limits (`Constraints.timeBounds`). */
58
58
  bounds?: TimeBounds | null;
59
59
  label: string;
60
+ /** The row's day, shown under the label (`timeRowDate`); null shows none. */
61
+ dateText?: string | null;
62
+ /** Replaces the label and its date (`renderTimeLabel`). */
63
+ renderLabel?: () => React.ReactNode;
60
64
  disabled?: boolean;
61
65
  tokens: ThemeTokens;
62
66
  styles?: PickerStyles;
@@ -77,6 +81,37 @@ const issueText = (
77
81
  return strings.errorTimeNotAllowed;
78
82
  };
79
83
 
84
+ /** The row's label: plain, with the day under it, or the app's own. */
85
+ const TimeLabel: React.FC<
86
+ Pick<
87
+ TimeRowProps,
88
+ 'label' | 'dateText' | 'renderLabel' | 'tokens' | 'styles' | 'testID'
89
+ >
90
+ > = ({ label, dateText, renderLabel, tokens, styles, testID }) => {
91
+ const s = buildTimeStyles(tokens);
92
+ if (!renderLabel && !dateText)
93
+ return <Text style={[s.label, styles?.timeLabel]}>{label}</Text>;
94
+ return (
95
+ <View style={[s.labelWrap, styles?.timeLabelWrap]}>
96
+ {renderLabel ? (
97
+ renderLabel()
98
+ ) : (
99
+ <>
100
+ <Text style={[s.label, s.labelInWrap, styles?.timeLabel]}>
101
+ {label}
102
+ </Text>
103
+ <Text
104
+ style={[s.labelDate, styles?.timeLabelDate]}
105
+ testID={testID ? `${testID}-date` : undefined}
106
+ >
107
+ {dateText}
108
+ </Text>
109
+ </>
110
+ )}
111
+ </View>
112
+ );
113
+ };
114
+
80
115
  /**
81
116
  * Label, the time boxes and the error below them. The boxes are
82
117
  * normal inputs (free cursor, digits only, two at most) with three helpers:
@@ -97,6 +132,8 @@ const TimeRow: React.FC<TimeRowProps> = ({
97
132
  minuteStep = 1,
98
133
  bounds,
99
134
  label,
135
+ dateText,
136
+ renderLabel,
100
137
  disabled,
101
138
  tokens,
102
139
  styles,
@@ -205,8 +242,11 @@ const TimeRow: React.FC<TimeRowProps> = ({
205
242
  let issue: TimeEntryIssue | null = effective.issue;
206
243
  if (effective.isEmpty) issue = time ? 'INCOMPLETE' : null;
207
244
  if (effective.time) issue = checkTime(effective.time, { minuteStep, bounds });
245
+ // A time set elsewhere reaches the boxes one render later (the effect
246
+ // above); until then the boxes are stale, so check that time instead.
247
+ const boxesStale = !focused && timeKey(time) !== lastKey.current;
208
248
  // The wheel always holds a complete time: only the limits and the step apply.
209
- if (input === 'wheel')
249
+ if (input === 'wheel' || boxesStale)
210
250
  issue = time ? checkTime(time, { minuteStep, bounds }) : null;
211
251
 
212
252
  const pending =
@@ -268,7 +308,14 @@ const TimeRow: React.FC<TimeRowProps> = ({
268
308
  return (
269
309
  <View style={s.wrapper} testID={testID}>
270
310
  <View style={[s.row, styles?.timeRow]}>
271
- <Text style={[s.label, styles?.timeLabel]}>{label}</Text>
311
+ <TimeLabel
312
+ label={label}
313
+ dateText={dateText}
314
+ renderLabel={renderLabel}
315
+ tokens={tokens}
316
+ styles={styles}
317
+ testID={testID}
318
+ />
272
319
  {input === 'wheel' ? (
273
320
  <TimeWheel
274
321
  time={time}
@@ -5,10 +5,11 @@ import type { Draft, DraftTarget } from '../../core/draft';
5
5
  import type { TimeEntryIssue } from '../../core/timeEntry';
6
6
  import type { CalendarDate, Time, TimeFormat } from '../../core/types';
7
7
  import type { RangeEnd } from '../field/RangeFields';
8
- import { formatDateText } from '../field/fieldText';
8
+ import { formatDateText, type DateTextFormatter } from '../field/fieldText';
9
9
  import type { PickerStrings } from '../theme/defaultStrings';
10
10
  import type { PickerStyles } from '../theme/styleKeys';
11
11
  import type { ThemeTokens } from '../theme/tokens';
12
+ import type { TimeLabelRenderProps } from '../types';
12
13
  import TimeRow from './TimeRow';
13
14
 
14
15
  /** Times typed before their day is picked, per end ('shared' lives in the draft). */
@@ -37,6 +38,11 @@ interface Props {
37
38
  onSpinChange: (spinning: boolean) => void;
38
39
  /** A row's box gained or lost focus. */
39
40
  onFocusChange?: (rowKey: string, focused: boolean) => void;
41
+ /** Shows each row's day under its label. */
42
+ timeRowDate?: boolean;
43
+ renderTimeLabel?: (p: TimeLabelRenderProps) => React.ReactNode;
44
+ /** A day as the field writes it (the Hijri text for a Hijri pick); default `dateFormat`. */
45
+ dateText?: DateTextFormatter;
40
46
  /** A row's time that is typed but not saved yet (null once saved or gone). */
41
47
  onPendingChange?: (
42
48
  rowKey: string,
@@ -51,6 +57,7 @@ interface Props {
51
57
 
52
58
  interface RowSpec {
53
59
  key: string;
60
+ kind: TimeLabelRenderProps['row'];
54
61
  label: string;
55
62
  time: Time | null;
56
63
  day: CalendarDate | null;
@@ -85,6 +92,9 @@ const TimeRows: React.FC<Props> = (props) => {
85
92
  onSpinChange,
86
93
  onFocusChange,
87
94
  onPendingChange,
95
+ timeRowDate = false,
96
+ renderTimeLabel,
97
+ dateText,
88
98
  tokens,
89
99
  styles,
90
100
  strings,
@@ -98,6 +108,7 @@ const TimeRows: React.FC<Props> = (props) => {
98
108
  ): RowSpec => {
99
109
  return {
100
110
  key,
111
+ kind: key,
101
112
  label,
102
113
  time: value?.time ?? pending[key] ?? null,
103
114
  day: value?.date ?? null,
@@ -123,6 +134,7 @@ const TimeRows: React.FC<Props> = (props) => {
123
134
  rows = [
124
135
  {
125
136
  key: 'shared',
137
+ kind: 'shared',
126
138
  label: strings.time,
127
139
  time: draft.sharedTime,
128
140
  day: null,
@@ -142,7 +154,11 @@ const TimeRows: React.FC<Props> = (props) => {
142
154
  rows = [
143
155
  {
144
156
  key: `day-${slot.date.year}-${slot.date.month}-${slot.date.day}`,
145
- label: `${strings.time} · ${formatDateText(slot, dateFormat)}`,
157
+ kind: 'day',
158
+ // With timeRowDate the day goes under the label instead.
159
+ label: timeRowDate
160
+ ? strings.time
161
+ : `${strings.time} · ${formatDateText(slot, dateFormat)}`,
146
162
  time: slot.time,
147
163
  day: slot.date,
148
164
  target: { day: slot.date },
@@ -200,6 +216,24 @@ const TimeRows: React.FC<Props> = (props) => {
200
216
  // eslint-disable-next-line react-hooks/exhaustive-deps -- reports changes only; the callback may be a new function on every render
201
217
  useEffect(() => onSpinChange(anySpinning), [anySpinning]);
202
218
 
219
+ /** A row's day as the field writes it, or null before it is picked. */
220
+ const dayText = (row: RowSpec): string | null => {
221
+ if (!row.day) return null;
222
+ const value = { date: row.day, time: row.time };
223
+ return dateText ? dateText(value) : formatDateText(value, dateFormat);
224
+ };
225
+ const labelRenderer = (row: RowSpec) =>
226
+ renderTimeLabel
227
+ ? () =>
228
+ renderTimeLabel({
229
+ label: row.label,
230
+ row: row.kind,
231
+ date: row.day,
232
+ dateText: dayText(row),
233
+ time: row.time,
234
+ })
235
+ : undefined;
236
+
203
237
  const single = rows.length === 1;
204
238
  return (
205
239
  <>
@@ -218,6 +252,8 @@ const TimeRows: React.FC<Props> = (props) => {
218
252
  minuteStep={minuteStep}
219
253
  bounds={row.day ? constraints.timeBounds(row.day) : null}
220
254
  label={row.label}
255
+ dateText={timeRowDate ? dayText(row) : null}
256
+ renderLabel={labelRenderer(row)}
221
257
  disabled={disabled}
222
258
  tokens={tokens}
223
259
  styles={styles}
@@ -23,13 +23,22 @@ const create = (t: ThemeTokens) =>
23
23
  fontSize: t.fontSizeLarge,
24
24
  color: t.heading,
25
25
  },
26
- // Times read hh:mm in every language, so the boxes don't mirror in RTL.
26
+ // The label with the day under it (timeRowDate), or a renderTimeLabel label.
27
+ labelWrap: { flexShrink: 1, marginEnd: 12 },
28
+ labelInWrap: { marginEnd: 0 },
29
+ labelDate: {
30
+ marginTop: 2,
31
+ ...fontStyle(t, 'regular'),
32
+ fontSize: t.fontSizeSmall,
33
+ color: t.mutedText,
34
+ },
27
35
  boxes: {
28
36
  flexDirection: 'row',
29
37
  alignItems: 'center',
30
38
  gap: 22,
31
- direction: 'ltr',
32
39
  },
40
+ // The boxes follow the row's direction: right to left (the Hijri tab, or a
41
+ // right-to-left app), the hour sits on the right and AM / PM on the left.
33
42
  clock: { flexDirection: 'row', alignItems: 'center' },
34
43
  // Each box is 50 × 46 with a 1 pt border and radius 6.
35
44
  boxWrap: { width: 50 },
@@ -62,9 +71,9 @@ const create = (t: ThemeTokens) =>
62
71
  },
63
72
  boxFocused: { borderColor: t.borderFocused },
64
73
  boxError: { borderColor: t.error },
74
+ // No height of its own, so `timeBox` sets the row's height.
65
75
  colonCell: {
66
76
  width: 24,
67
- height: 46,
68
77
  alignItems: 'center',
69
78
  justifyContent: 'center',
70
79
  },
@@ -104,7 +113,6 @@ const create = (t: ThemeTokens) =>
104
113
  flexDirection: 'row',
105
114
  alignItems: 'center',
106
115
  alignSelf: 'center',
107
- direction: 'ltr',
108
116
  },
109
117
  wheelColumn: { width: 64, height: 200, overflow: 'hidden' },
110
118
  wheelHighlight: {
@@ -165,6 +165,20 @@ export interface LabelRenderProps {
165
165
  fieldTestID?: string;
166
166
  }
167
167
 
168
+ /** What `renderTimeLabel` receives, for one time row. */
169
+ export interface TimeLabelRenderProps {
170
+ /** The row's label from `strings`: 'Time', 'Start time' or 'End time'. */
171
+ label: string;
172
+ /** A single value's row, a range end, the multiple-mode shared row, or a day's own row (`timeMode: 'perDate'`). */
173
+ row: 'single' | 'from' | 'to' | 'shared' | 'day';
174
+ /** The row's picked day; null before it is picked, and for the shared row. */
175
+ date: CalendarDate | null;
176
+ /** That day as the field writes it; null when there is no day. Set whether or not `timeRowDate` is on. */
177
+ dateText: string | null;
178
+ /** The row's time, or null. */
179
+ time: Time | null;
180
+ }
181
+
168
182
  /** What `renderInstruction` receives. */
169
183
  export interface InstructionRenderProps {
170
184
  instruction: string;
@@ -368,6 +382,14 @@ export interface DateTimePickerProps {
368
382
  minuteStep?: number;
369
383
  /** A newly picked day always gets this time (over a time typed first and over the current time). */
370
384
  newDayTime?: Time;
385
+ /**
386
+ * Shows each time row's picked day under its label ('Start time',
387
+ * 10-03-2026), as the field writes it. Nothing shows until the day is
388
+ * picked. Style it with `timeLabelDate`. Default false.
389
+ */
390
+ timeRowDate?: boolean;
391
+ /** Replaces a time row's label (and its date). The boxes and errors stay built in. */
392
+ renderTimeLabel?: (p: TimeLabelRenderProps) => React.ReactNode;
371
393
 
372
394
  // limits → greyed days and errors
373
395
  showHolidays?: boolean;
@@ -391,6 +413,13 @@ export interface DateTimePickerProps {
391
413
  theme?: Partial<ThemeTokens>;
392
414
  styles?: PickerStyles;
393
415
  strings?: Partial<PickerStrings>;
416
+ /**
417
+ * The texts while the Hijri tab shows (e.g. Arabic), for its calendar and
418
+ * time rows, the error messages inside the picker, and a date picked in it
419
+ * (the on-screen field's errors keep `strings`). A key left out (or `undefined`)
420
+ * keeps AM / PM in `hijriLocale` (ص / م), then `strings`.
421
+ */
422
+ hijriStrings?: Partial<PickerStrings>;
394
423
  testID?: string;
395
424
 
396
425
  // events
@@ -255,6 +255,48 @@ const writeHijriDate = (d: CalendarDate, locale: string): string | null => {
255
255
  return `${h.day} ${hijriMonthNames(locale)[h.month - 1]} ${h.year}`;
256
256
  };
257
257
 
258
+ export interface Periods {
259
+ am: string;
260
+ pm: string;
261
+ }
262
+
263
+ const ARABIC_PERIODS: Periods = { am: 'ص', pm: 'م' };
264
+ const periodCache = new Map<string, Periods | null>();
265
+
266
+ /**
267
+ * AM and PM in the Hijri locale's language, as Intl writes them ('ar-SA' →
268
+ * ص / م). An Arabic locale whose engine gives none, or Latin ones, gets ص / م;
269
+ * another locale gets null (the picker keeps strings.am / pm).
270
+ */
271
+ export const hijriPeriods = (locale: string): Periods | null => {
272
+ if (!periodCache.has(locale)) periodCache.set(locale, readPeriods(locale));
273
+ return periodCache.get(locale)!;
274
+ };
275
+
276
+ const readPeriods = (locale: string): Periods | null => {
277
+ const arabic = /^ar\b/i.test(locale);
278
+ try {
279
+ const f = new Intl.DateTimeFormat(locale, {
280
+ timeZone: 'UTC',
281
+ hour: 'numeric',
282
+ hour12: true,
283
+ });
284
+ const period = (hour: number) =>
285
+ f
286
+ .formatToParts(utcDate(2026, 0, 1, hour))
287
+ // Some engines write the part type in lower case.
288
+ .find((p) => p.type.toLowerCase() === 'dayperiod')
289
+ ?.value.trim();
290
+ const am = period(9);
291
+ const pm = period(21);
292
+ if (am && pm && am !== pm && !(arabic && /[A-Za-z]/.test(am + pm)))
293
+ return { am, pm };
294
+ } catch {
295
+ // An engine without this locale falls through.
296
+ }
297
+ return arabic ? ARABIC_PERIODS : null;
298
+ };
299
+
258
300
  /**
259
301
  * The system the picker uses: the requested one, Gregorian by default.
260
302
  * Anything Hijri falls back to Gregorian when unsupported.
package/src/core/zoned.ts CHANGED
@@ -1,9 +1,16 @@
1
1
  import { DateTime } from 'luxon';
2
2
  import type { DateTimeValue } from './types';
3
3
 
4
- /** The Luxon DateTime for a date-time value in `zone` (seconds and ms zeroed). */
5
- export const toZonedDateTime = (value: DateTimeValue, zone: string): DateTime =>
6
- DateTime.fromObject(
4
+ /**
5
+ * The Luxon DateTime for a date-time value in `zone` (seconds and ms zeroed).
6
+ * A time that happens twice resolves to its first occurrence: Luxon alone picks
7
+ * by the zone's offset today, so the result would change with the season.
8
+ */
9
+ export const toZonedDateTime = (
10
+ value: DateTimeValue,
11
+ zone: string
12
+ ): DateTime => {
13
+ const dt = DateTime.fromObject(
7
14
  {
8
15
  ...value.date,
9
16
  hour: value.time?.hour ?? 0,
@@ -13,6 +20,11 @@ export const toZonedDateTime = (value: DateTimeValue, zone: string): DateTime =>
13
20
  },
14
21
  { zone }
15
22
  );
23
+ if (!dt.isValid) return dt;
24
+ return dt
25
+ .getPossibleOffsets()
26
+ .reduce((first, d) => (d.toMillis() < first.toMillis() ? d : first), dt);
27
+ };
16
28
 
17
29
  export type DstStatus = 'ok' | 'gap' | 'overlap';
18
30
 
package/src/index.tsx CHANGED
@@ -31,7 +31,7 @@ export {
31
31
  todayIn,
32
32
  weekdayOf,
33
33
  } from './core/calendarDate';
34
- export { hijriDateText } from './core/calendarSystem';
34
+ export { hijriDateText, hijriPeriods } from './core/calendarSystem';
35
35
  export { arabicDigits } from './core/numerals';
36
36
 
37
37
  // Types
@@ -70,8 +70,9 @@ export type {
70
70
  SheetRenderProps,
71
71
  SheetSize,
72
72
  Slot,
73
+ TimeLabelRenderProps,
73
74
  } from './components/types';
74
- export type { CalendarSystem } from './core/calendarSystem';
75
+ export type { CalendarSystem, Periods } from './core/calendarSystem';
75
76
  export type { Numerals } from './core/numerals';
76
77
  export type { PickerStrings } from './components/theme/defaultStrings';
77
78
  export type {