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.
- package/CHANGELOG.md +44 -0
- package/lib/commonjs/components/DateTimePicker.js +55 -13
- package/lib/commonjs/components/DateTimePicker.js.map +1 -1
- package/lib/commonjs/components/calendar/CalendarHeader.js +3 -1
- package/lib/commonjs/components/calendar/CalendarHeader.js.map +1 -1
- package/lib/commonjs/components/calendar/DayGrid.js +4 -1
- package/lib/commonjs/components/calendar/DayGrid.js.map +1 -1
- package/lib/commonjs/components/calendar/calendarStyles.js +2 -1
- package/lib/commonjs/components/calendar/calendarStyles.js.map +1 -1
- package/lib/commonjs/components/field/RangeFields.js +2 -0
- package/lib/commonjs/components/field/RangeFields.js.map +1 -1
- package/lib/commonjs/components/field/SegmentedInput.js +1 -1
- package/lib/commonjs/components/field/SegmentedInput.js.map +1 -1
- package/lib/commonjs/components/field/ValueField.js +57 -5
- package/lib/commonjs/components/field/ValueField.js.map +1 -1
- package/lib/commonjs/components/field/fieldStyles.js +17 -0
- package/lib/commonjs/components/field/fieldStyles.js.map +1 -1
- package/lib/commonjs/components/field/fieldText.js +12 -3
- package/lib/commonjs/components/field/fieldText.js.map +1 -1
- package/lib/commonjs/components/theme/defaultStrings.js +6 -2
- package/lib/commonjs/components/theme/defaultStrings.js.map +1 -1
- package/lib/commonjs/components/theme/direction.js +17 -0
- package/lib/commonjs/components/theme/direction.js.map +1 -0
- package/lib/commonjs/components/theme/styleKeys.js +9 -2
- package/lib/commonjs/components/theme/styleKeys.js.map +1 -1
- package/lib/commonjs/components/time/TimeRow.js +42 -4
- package/lib/commonjs/components/time/TimeRow.js.map +1 -1
- package/lib/commonjs/components/time/TimeRows.js +27 -1
- package/lib/commonjs/components/time/TimeRows.js.map +1 -1
- package/lib/commonjs/components/time/timeStyles.js +19 -6
- package/lib/commonjs/components/time/timeStyles.js.map +1 -1
- package/lib/commonjs/components/types.js +2 -0
- package/lib/commonjs/components/types.js.map +1 -1
- package/lib/commonjs/core/calendarSystem.js +39 -1
- package/lib/commonjs/core/calendarSystem.js.map +1 -1
- package/lib/commonjs/core/zoned.js +18 -10
- package/lib/commonjs/core/zoned.js.map +1 -1
- package/lib/commonjs/index.js +6 -0
- package/lib/commonjs/index.js.map +1 -1
- package/lib/module/components/DateTimePicker.js +57 -15
- package/lib/module/components/DateTimePicker.js.map +1 -1
- package/lib/module/components/calendar/CalendarHeader.js +4 -2
- package/lib/module/components/calendar/CalendarHeader.js.map +1 -1
- package/lib/module/components/calendar/DayGrid.js +5 -2
- package/lib/module/components/calendar/DayGrid.js.map +1 -1
- package/lib/module/components/calendar/calendarStyles.js +2 -1
- package/lib/module/components/calendar/calendarStyles.js.map +1 -1
- package/lib/module/components/field/RangeFields.js +2 -0
- package/lib/module/components/field/RangeFields.js.map +1 -1
- package/lib/module/components/field/SegmentedInput.js +1 -1
- package/lib/module/components/field/SegmentedInput.js.map +1 -1
- package/lib/module/components/field/ValueField.js +59 -7
- package/lib/module/components/field/ValueField.js.map +1 -1
- package/lib/module/components/field/fieldStyles.js +17 -0
- package/lib/module/components/field/fieldStyles.js.map +1 -1
- package/lib/module/components/field/fieldText.js +10 -2
- package/lib/module/components/field/fieldText.js.map +1 -1
- package/lib/module/components/theme/defaultStrings.js +4 -1
- package/lib/module/components/theme/defaultStrings.js.map +1 -1
- package/lib/module/components/theme/direction.js +13 -0
- package/lib/module/components/theme/direction.js.map +1 -0
- package/lib/module/components/theme/styleKeys.js +9 -2
- package/lib/module/components/theme/styleKeys.js.map +1 -1
- package/lib/module/components/time/TimeRow.js +43 -5
- package/lib/module/components/time/TimeRow.js.map +1 -1
- package/lib/module/components/time/TimeRows.js +27 -1
- package/lib/module/components/time/TimeRows.js.map +1 -1
- package/lib/module/components/time/timeStyles.js +19 -6
- package/lib/module/components/time/timeStyles.js.map +1 -1
- package/lib/module/components/types.js +2 -0
- package/lib/module/components/types.js.map +1 -1
- package/lib/module/core/calendarSystem.js +37 -0
- package/lib/module/core/calendarSystem.js.map +1 -1
- package/lib/module/core/zoned.js +18 -10
- package/lib/module/core/zoned.js.map +1 -1
- package/lib/module/index.js +1 -1
- package/lib/module/index.js.map +1 -1
- package/lib/typescript/commonjs/src/components/DateTimePicker.d.ts.map +1 -1
- package/lib/typescript/commonjs/src/components/calendar/CalendarHeader.d.ts.map +1 -1
- package/lib/typescript/commonjs/src/components/calendar/DayGrid.d.ts.map +1 -1
- package/lib/typescript/commonjs/src/components/calendar/calendarStyles.d.ts +1 -1
- package/lib/typescript/commonjs/src/components/calendar/calendarStyles.d.ts.map +1 -1
- package/lib/typescript/commonjs/src/components/field/RangeFields.d.ts +2 -0
- package/lib/typescript/commonjs/src/components/field/RangeFields.d.ts.map +1 -1
- package/lib/typescript/commonjs/src/components/field/ValueField.d.ts +4 -0
- package/lib/typescript/commonjs/src/components/field/ValueField.d.ts.map +1 -1
- package/lib/typescript/commonjs/src/components/field/fieldStyles.d.ts +15 -0
- package/lib/typescript/commonjs/src/components/field/fieldStyles.d.ts.map +1 -1
- package/lib/typescript/commonjs/src/components/field/fieldText.d.ts +5 -0
- package/lib/typescript/commonjs/src/components/field/fieldText.d.ts.map +1 -1
- package/lib/typescript/commonjs/src/components/theme/defaultStrings.d.ts +2 -0
- package/lib/typescript/commonjs/src/components/theme/defaultStrings.d.ts.map +1 -1
- package/lib/typescript/commonjs/src/components/theme/direction.d.ts +8 -0
- package/lib/typescript/commonjs/src/components/theme/direction.d.ts.map +1 -0
- package/lib/typescript/commonjs/src/components/theme/styleKeys.d.ts +1 -1
- package/lib/typescript/commonjs/src/components/theme/styleKeys.d.ts.map +1 -1
- package/lib/typescript/commonjs/src/components/time/TimeRow.d.ts +4 -0
- package/lib/typescript/commonjs/src/components/time/TimeRow.d.ts.map +1 -1
- package/lib/typescript/commonjs/src/components/time/TimeRows.d.ts +7 -0
- package/lib/typescript/commonjs/src/components/time/TimeRows.d.ts.map +1 -1
- package/lib/typescript/commonjs/src/components/time/timeStyles.d.ts +24 -3
- package/lib/typescript/commonjs/src/components/time/timeStyles.d.ts.map +1 -1
- package/lib/typescript/commonjs/src/components/types.d.ts +28 -0
- package/lib/typescript/commonjs/src/components/types.d.ts.map +1 -1
- package/lib/typescript/commonjs/src/core/calendarSystem.d.ts +10 -0
- package/lib/typescript/commonjs/src/core/calendarSystem.d.ts.map +1 -1
- package/lib/typescript/commonjs/src/core/zoned.d.ts +5 -1
- package/lib/typescript/commonjs/src/core/zoned.d.ts.map +1 -1
- package/lib/typescript/commonjs/src/index.d.ts +3 -3
- package/lib/typescript/commonjs/src/index.d.ts.map +1 -1
- package/lib/typescript/module/src/components/DateTimePicker.d.ts.map +1 -1
- package/lib/typescript/module/src/components/calendar/CalendarHeader.d.ts.map +1 -1
- package/lib/typescript/module/src/components/calendar/DayGrid.d.ts.map +1 -1
- package/lib/typescript/module/src/components/calendar/calendarStyles.d.ts +1 -1
- package/lib/typescript/module/src/components/calendar/calendarStyles.d.ts.map +1 -1
- package/lib/typescript/module/src/components/field/RangeFields.d.ts +2 -0
- package/lib/typescript/module/src/components/field/RangeFields.d.ts.map +1 -1
- package/lib/typescript/module/src/components/field/ValueField.d.ts +4 -0
- package/lib/typescript/module/src/components/field/ValueField.d.ts.map +1 -1
- package/lib/typescript/module/src/components/field/fieldStyles.d.ts +15 -0
- package/lib/typescript/module/src/components/field/fieldStyles.d.ts.map +1 -1
- package/lib/typescript/module/src/components/field/fieldText.d.ts +5 -0
- package/lib/typescript/module/src/components/field/fieldText.d.ts.map +1 -1
- package/lib/typescript/module/src/components/theme/defaultStrings.d.ts +2 -0
- package/lib/typescript/module/src/components/theme/defaultStrings.d.ts.map +1 -1
- package/lib/typescript/module/src/components/theme/direction.d.ts +8 -0
- package/lib/typescript/module/src/components/theme/direction.d.ts.map +1 -0
- package/lib/typescript/module/src/components/theme/styleKeys.d.ts +1 -1
- package/lib/typescript/module/src/components/theme/styleKeys.d.ts.map +1 -1
- package/lib/typescript/module/src/components/time/TimeRow.d.ts +4 -0
- package/lib/typescript/module/src/components/time/TimeRow.d.ts.map +1 -1
- package/lib/typescript/module/src/components/time/TimeRows.d.ts +7 -0
- package/lib/typescript/module/src/components/time/TimeRows.d.ts.map +1 -1
- package/lib/typescript/module/src/components/time/timeStyles.d.ts +24 -3
- package/lib/typescript/module/src/components/time/timeStyles.d.ts.map +1 -1
- package/lib/typescript/module/src/components/types.d.ts +28 -0
- package/lib/typescript/module/src/components/types.d.ts.map +1 -1
- package/lib/typescript/module/src/core/calendarSystem.d.ts +10 -0
- package/lib/typescript/module/src/core/calendarSystem.d.ts.map +1 -1
- package/lib/typescript/module/src/core/zoned.d.ts +5 -1
- package/lib/typescript/module/src/core/zoned.d.ts.map +1 -1
- package/lib/typescript/module/src/index.d.ts +3 -3
- package/lib/typescript/module/src/index.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/components/DateTimePicker.tsx +97 -47
- package/src/components/calendar/CalendarHeader.tsx +3 -2
- package/src/components/calendar/DayGrid.tsx +5 -2
- package/src/components/calendar/calendarStyles.ts +2 -1
- package/src/components/field/RangeFields.tsx +4 -0
- package/src/components/field/SegmentedInput.tsx +1 -1
- package/src/components/field/ValueField.tsx +83 -15
- package/src/components/field/fieldStyles.ts +6 -0
- package/src/components/field/fieldText.ts +25 -4
- package/src/components/theme/defaultStrings.ts +11 -1
- package/src/components/theme/direction.ts +12 -0
- package/src/components/theme/styleKeys.ts +9 -0
- package/src/components/time/TimeRow.tsx +49 -2
- package/src/components/time/TimeRows.tsx +38 -2
- package/src/components/time/timeStyles.ts +12 -4
- package/src/components/types.ts +29 -0
- package/src/core/calendarSystem.ts +42 -0
- package/src/core/zoned.ts +15 -3
- 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 {
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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) ?
|
|
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 =
|
|
1401
|
+
rangeError = shownStrings.errorNotAllowed;
|
|
1373
1402
|
else if (previewIssue === 'RANGE_ORDER')
|
|
1374
|
-
rangeError =
|
|
1403
|
+
rangeError = shownStrings.errorRangeOrder;
|
|
1375
1404
|
else if (previewIssue === 'RANGE_LENGTH')
|
|
1376
1405
|
rangeError =
|
|
1377
1406
|
range?.minDays !== undefined && length < range.minDays
|
|
1378
|
-
?
|
|
1379
|
-
:
|
|
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
|
-
|
|
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
|
-
|
|
1565
|
-
|
|
1566
|
-
|
|
1567
|
-
|
|
1568
|
-
|
|
1569
|
-
|
|
1570
|
-
|
|
1571
|
-
|
|
1572
|
-
|
|
1573
|
-
|
|
1574
|
-
|
|
1575
|
-
|
|
1576
|
-
|
|
1577
|
-
|
|
1578
|
-
|
|
1579
|
-
|
|
1580
|
-
|
|
1581
|
-
|
|
1582
|
-
|
|
1583
|
-
|
|
1584
|
-
|
|
1585
|
-
|
|
1586
|
-
|
|
1587
|
-
|
|
1588
|
-
|
|
1589
|
-
|
|
1590
|
-
|
|
1591
|
-
|
|
1592
|
-
|
|
1593
|
-
|
|
1594
|
-
|
|
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={
|
|
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
|
|
1642
|
-
//
|
|
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(
|
|
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') !==
|
|
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 {
|
|
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,
|
|
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
|
-
|
|
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,
|
|
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
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
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
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
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;
|