rn-grid-date-time-picker 1.2.2 → 1.3.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 +35 -0
- package/lib/commonjs/components/DateTimePicker.js +109 -129
- package/lib/commonjs/components/DateTimePicker.js.map +1 -1
- package/lib/commonjs/components/calendar/DayCell.js +17 -6
- package/lib/commonjs/components/calendar/DayCell.js.map +1 -1
- package/lib/commonjs/components/calendar/calendarStyles.js +11 -0
- package/lib/commonjs/components/calendar/calendarStyles.js.map +1 -1
- package/lib/commonjs/components/container/PickerContainer.js +2 -1
- package/lib/commonjs/components/container/PickerContainer.js.map +1 -1
- package/lib/commonjs/components/container/PickerLayout.js +5 -1
- package/lib/commonjs/components/container/PickerLayout.js.map +1 -1
- package/lib/commonjs/components/field/ValueField.js +19 -2
- package/lib/commonjs/components/field/ValueField.js.map +1 -1
- package/lib/commonjs/components/field/fieldText.js +44 -5
- package/lib/commonjs/components/field/fieldText.js.map +1 -1
- package/lib/commonjs/components/hooks/useScrollToTime.js +85 -0
- package/lib/commonjs/components/hooks/useScrollToTime.js.map +1 -0
- package/lib/commonjs/components/theme/styleKeys.js +3 -1
- package/lib/commonjs/components/theme/styleKeys.js.map +1 -1
- package/lib/commonjs/components/time/TypedTime.js +20 -2
- package/lib/commonjs/components/time/TypedTime.js.map +1 -1
- package/lib/commonjs/components/types.js +4 -1
- package/lib/commonjs/components/types.js.map +1 -1
- package/lib/commonjs/core/draft.js +2 -1
- package/lib/commonjs/core/draft.js.map +1 -1
- package/lib/module/components/DateTimePicker.js +110 -130
- package/lib/module/components/DateTimePicker.js.map +1 -1
- package/lib/module/components/calendar/DayCell.js +18 -7
- package/lib/module/components/calendar/DayCell.js.map +1 -1
- package/lib/module/components/calendar/calendarStyles.js +11 -0
- package/lib/module/components/calendar/calendarStyles.js.map +1 -1
- package/lib/module/components/container/PickerContainer.js +2 -1
- package/lib/module/components/container/PickerContainer.js.map +1 -1
- package/lib/module/components/container/PickerLayout.js +5 -1
- package/lib/module/components/container/PickerLayout.js.map +1 -1
- package/lib/module/components/field/ValueField.js +20 -3
- package/lib/module/components/field/ValueField.js.map +1 -1
- package/lib/module/components/field/fieldText.js +40 -4
- package/lib/module/components/field/fieldText.js.map +1 -1
- package/lib/module/components/hooks/useScrollToTime.js +79 -0
- package/lib/module/components/hooks/useScrollToTime.js.map +1 -0
- package/lib/module/components/theme/styleKeys.js +3 -1
- package/lib/module/components/theme/styleKeys.js.map +1 -1
- package/lib/module/components/time/TypedTime.js +22 -4
- package/lib/module/components/time/TypedTime.js.map +1 -1
- package/lib/module/components/types.js +4 -1
- package/lib/module/components/types.js.map +1 -1
- package/lib/module/core/draft.js +2 -1
- package/lib/module/core/draft.js.map +1 -1
- package/lib/typescript/commonjs/src/components/DateTimePicker.d.ts.map +1 -1
- package/lib/typescript/commonjs/src/components/calendar/DayCell.d.ts.map +1 -1
- package/lib/typescript/commonjs/src/components/calendar/calendarStyles.d.ts +9 -0
- package/lib/typescript/commonjs/src/components/calendar/calendarStyles.d.ts.map +1 -1
- package/lib/typescript/commonjs/src/components/container/PickerContainer.d.ts.map +1 -1
- package/lib/typescript/commonjs/src/components/container/PickerLayout.d.ts +7 -2
- package/lib/typescript/commonjs/src/components/container/PickerLayout.d.ts.map +1 -1
- package/lib/typescript/commonjs/src/components/field/ValueField.d.ts.map +1 -1
- package/lib/typescript/commonjs/src/components/field/fieldText.d.ts +19 -0
- package/lib/typescript/commonjs/src/components/field/fieldText.d.ts.map +1 -1
- package/lib/typescript/commonjs/src/components/hooks/useScrollToTime.d.ts +26 -0
- package/lib/typescript/commonjs/src/components/hooks/useScrollToTime.d.ts.map +1 -0
- package/lib/typescript/commonjs/src/components/nativeTypes.d.ts +2 -1
- package/lib/typescript/commonjs/src/components/nativeTypes.d.ts.map +1 -1
- package/lib/typescript/commonjs/src/components/theme/styleKeys.d.ts.map +1 -1
- package/lib/typescript/commonjs/src/components/time/TypedTime.d.ts.map +1 -1
- package/lib/typescript/commonjs/src/components/types.d.ts +59 -8
- package/lib/typescript/commonjs/src/components/types.d.ts.map +1 -1
- package/lib/typescript/commonjs/src/core/draft.d.ts +2 -1
- package/lib/typescript/commonjs/src/core/draft.d.ts.map +1 -1
- package/lib/typescript/commonjs/src/index.d.ts +1 -1
- 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/DayCell.d.ts.map +1 -1
- package/lib/typescript/module/src/components/calendar/calendarStyles.d.ts +9 -0
- package/lib/typescript/module/src/components/calendar/calendarStyles.d.ts.map +1 -1
- package/lib/typescript/module/src/components/container/PickerContainer.d.ts.map +1 -1
- package/lib/typescript/module/src/components/container/PickerLayout.d.ts +7 -2
- package/lib/typescript/module/src/components/container/PickerLayout.d.ts.map +1 -1
- package/lib/typescript/module/src/components/field/ValueField.d.ts.map +1 -1
- package/lib/typescript/module/src/components/field/fieldText.d.ts +19 -0
- package/lib/typescript/module/src/components/field/fieldText.d.ts.map +1 -1
- package/lib/typescript/module/src/components/hooks/useScrollToTime.d.ts +26 -0
- package/lib/typescript/module/src/components/hooks/useScrollToTime.d.ts.map +1 -0
- package/lib/typescript/module/src/components/nativeTypes.d.ts +2 -1
- package/lib/typescript/module/src/components/nativeTypes.d.ts.map +1 -1
- package/lib/typescript/module/src/components/theme/styleKeys.d.ts.map +1 -1
- package/lib/typescript/module/src/components/time/TypedTime.d.ts.map +1 -1
- package/lib/typescript/module/src/components/types.d.ts +59 -8
- package/lib/typescript/module/src/components/types.d.ts.map +1 -1
- package/lib/typescript/module/src/core/draft.d.ts +2 -1
- package/lib/typescript/module/src/core/draft.d.ts.map +1 -1
- package/lib/typescript/module/src/index.d.ts +1 -1
- package/lib/typescript/module/src/index.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/components/DateTimePicker.tsx +141 -135
- package/src/components/calendar/DayCell.tsx +25 -6
- package/src/components/calendar/calendarStyles.ts +11 -0
- package/src/components/container/PickerContainer.tsx +1 -0
- package/src/components/container/PickerLayout.tsx +10 -1
- package/src/components/field/ValueField.tsx +30 -11
- package/src/components/field/fieldText.ts +48 -5
- package/src/components/hooks/useScrollToTime.ts +99 -0
- package/src/components/nativeTypes.ts +2 -1
- package/src/components/theme/styleKeys.ts +3 -1
- package/src/components/time/TypedTime.tsx +25 -6
- package/src/components/types.ts +63 -8
- package/src/core/draft.ts +5 -2
- package/src/index.tsx +1 -0
|
@@ -6,7 +6,7 @@ import { View } from 'react-native';
|
|
|
6
6
|
import { resolveAdapter } from "../adapters/index.js";
|
|
7
7
|
import { compareCalendarDates, daysBetween, isSameDay } from "../core/calendarDate.js";
|
|
8
8
|
import { createConstraints } from "../core/constraints.js";
|
|
9
|
-
import {
|
|
9
|
+
import { createDraft, DEFAULT_TIME, draftToWire, sameDayRangeOk, setTime as applyTime, validateDraft } from "../core/draft.js";
|
|
10
10
|
import { hijriDateText, hijriPeriods, isHijriSupported, resolveCalendarSystem } from "../core/calendarSystem.js";
|
|
11
11
|
import { defaultLocale, deriveDateFormat, deriveFirstDayOfWeek, deriveTimeFormat, normalizeDateFormat } from "../core/locale.js";
|
|
12
12
|
import { arabicDigits, latinDigits, localeDigits } from "../core/numerals.js";
|
|
@@ -17,7 +17,6 @@ import CalendarSwitch from "./calendar/CalendarSwitch.js";
|
|
|
17
17
|
import { localeNames } from "./calendar/localeNames.js";
|
|
18
18
|
import { RtlContext } from "./theme/direction.js";
|
|
19
19
|
import { NumeralsContext } from "./theme/numerals.js";
|
|
20
|
-
import { firstSelectedDay } from "./calendar/selection.js";
|
|
21
20
|
import PickerContainer from "./container/PickerContainer.js";
|
|
22
21
|
import { formatDateText, formatMultipleText, formatRangeText, formatValueText } from "./field/fieldText.js";
|
|
23
22
|
import FieldMessages from "./field/FieldMessages.js";
|
|
@@ -25,6 +24,7 @@ import RangeFields from "./field/RangeFields.js";
|
|
|
25
24
|
import ValueField from "./field/ValueField.js";
|
|
26
25
|
import { useKeyboardFold } from "./hooks/useKeyboardFold.js";
|
|
27
26
|
import { useOpenPicker } from "./hooks/useOpenPicker.js";
|
|
27
|
+
import { useScrollToTime } from "./hooks/useScrollToTime.js";
|
|
28
28
|
import { usePickerDraft } from "./hooks/usePickerDraft.js";
|
|
29
29
|
import { useTodayTick } from "./hooks/useTodayTick.js";
|
|
30
30
|
import TimeRows from "./time/TimeRows.js";
|
|
@@ -40,19 +40,6 @@ const RTL = {
|
|
|
40
40
|
direction: 'rtl'
|
|
41
41
|
};
|
|
42
42
|
|
|
43
|
-
/** The same kind of selection with nothing in it. */
|
|
44
|
-
const emptySelection = sel => sel.kind === 'single' ? {
|
|
45
|
-
kind: 'single',
|
|
46
|
-
day: null
|
|
47
|
-
} : sel.kind === 'range' ? {
|
|
48
|
-
kind: 'range',
|
|
49
|
-
from: null,
|
|
50
|
-
to: null
|
|
51
|
-
} : {
|
|
52
|
-
...sel,
|
|
53
|
-
days: []
|
|
54
|
-
};
|
|
55
|
-
|
|
56
43
|
/** Minute steps that divide the hour. */
|
|
57
44
|
const MINUTE_STEPS = [1, 2, 3, 4, 5, 6, 10, 12, 15, 20, 30, 60];
|
|
58
45
|
|
|
@@ -89,6 +76,7 @@ const DateTimePicker = props => {
|
|
|
89
76
|
multiple,
|
|
90
77
|
range,
|
|
91
78
|
presentation = 'sheet',
|
|
79
|
+
scrollToTimeRef,
|
|
92
80
|
visible,
|
|
93
81
|
onOpen,
|
|
94
82
|
onClose,
|
|
@@ -139,6 +127,7 @@ const DateTimePicker = props => {
|
|
|
139
127
|
calendarSwitch,
|
|
140
128
|
onCalendarSystemChange,
|
|
141
129
|
dualDefaultTab = 'gregory',
|
|
130
|
+
fieldCalendar = 'picked',
|
|
142
131
|
hijriLocale = 'ar-SA',
|
|
143
132
|
renderCalendarSwitch,
|
|
144
133
|
showToday = false,
|
|
@@ -329,12 +318,13 @@ const DateTimePicker = props => {
|
|
|
329
318
|
const folded = keyboardUp && (!isInline || ownFocus);
|
|
330
319
|
const rangeLayout = range?.layout ?? 'singleField';
|
|
331
320
|
const multipleTimeMode = multiple?.timeMode ?? 'shared';
|
|
321
|
+
const startsEmpty = newDayTime === 'empty';
|
|
332
322
|
const draftOptions = useMemo(() => {
|
|
333
323
|
return {
|
|
334
324
|
type,
|
|
335
325
|
zone,
|
|
336
326
|
adapter,
|
|
337
|
-
defaultTime: DEFAULT_TIME,
|
|
327
|
+
defaultTime: startsEmpty ? null : DEFAULT_TIME,
|
|
338
328
|
multiple: {
|
|
339
329
|
timeMode: multiple?.timeMode,
|
|
340
330
|
maxSelections: multiple?.maxSelections
|
|
@@ -346,7 +336,7 @@ const DateTimePicker = props => {
|
|
|
346
336
|
maxDays: range?.maxDays
|
|
347
337
|
}
|
|
348
338
|
};
|
|
349
|
-
}, [type, zone, adapter, multiple?.timeMode, multiple?.maxSelections, range?.allowSameDay, range?.reselect, range?.minDays, range?.maxDays]);
|
|
339
|
+
}, [type, zone, adapter, startsEmpty, multiple?.timeMode, multiple?.maxSelections, range?.allowSameDay, range?.reselect, range?.minDays, range?.maxDays]);
|
|
350
340
|
const draft = usePickerDraft(mode, value, isOpen, draftOptions);
|
|
351
341
|
const today = useTodayTick(zone, now);
|
|
352
342
|
|
|
@@ -521,6 +511,15 @@ const DateTimePicker = props => {
|
|
|
521
511
|
const preview = useMemo(() => Object.values(rowPending).reduce((acc, p) => p ? applyTime(acc, p.time, p.target) : acc, draft.draft), [draft.draft, rowPending]);
|
|
522
512
|
const previewIssue = validateDraft(preview, draftOptions);
|
|
523
513
|
const d = draft.draft;
|
|
514
|
+
// Once a calendar tap picks the date (both ends of a range), the sheet (or
|
|
515
|
+
// the app's ScrollView around an inline picker) scrolls the time section
|
|
516
|
+
// into view.
|
|
517
|
+
const datePicked = d.mode === 'single' ? !!d.slot.value : d.mode === 'range' ? !!d.from.value && !!d.to.value : d.slots.length > 0;
|
|
518
|
+
const {
|
|
519
|
+
scrollRef,
|
|
520
|
+
timeRef,
|
|
521
|
+
request: revealTime
|
|
522
|
+
} = useScrollToTime(type !== 'datetime' ? false : isInline ? !!scrollToTimeRef : sheetOptions?.scrollToTime ?? true, datePicked, isInline ? scrollToTimeRef : undefined);
|
|
524
523
|
/** The values a draft would save, with a shared time applied. */
|
|
525
524
|
const valuesOf = dr => {
|
|
526
525
|
if (dr.mode === 'single') return [dr.slot.value];
|
|
@@ -557,10 +556,10 @@ const DateTimePicker = props => {
|
|
|
557
556
|
};
|
|
558
557
|
const valueKey = JSON.stringify(value ?? '');
|
|
559
558
|
|
|
560
|
-
// A selection
|
|
561
|
-
//
|
|
562
|
-
// value remembers its calendar; one from outside counts as picked in
|
|
563
|
-
// calendar the picker opens on.
|
|
559
|
+
// A selection shows in both tabs (Gregorian and Hijri). It is saved with the
|
|
560
|
+
// calendar on screen, and the on-screen field writes it in that calendar. A
|
|
561
|
+
// saved value remembers its calendar; one from outside counts as picked in
|
|
562
|
+
// the calendar the picker opens on.
|
|
564
563
|
// The saved value as a draft, for the on-screen field and for comparing
|
|
565
564
|
// values by what they hold.
|
|
566
565
|
const saved = useMemo(() => createDraft(mode, value, draftOptions),
|
|
@@ -571,32 +570,12 @@ const DateTimePicker = props => {
|
|
|
571
570
|
const savedContent = contentOf(saved);
|
|
572
571
|
const savedSystemRef = useRef(null);
|
|
573
572
|
const savedSystem = savedSystemRef.current?.content === savedContent ? savedSystemRef.current.system : outsideSystem;
|
|
574
|
-
const
|
|
575
|
-
//
|
|
576
|
-
//
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
valueKey,
|
|
581
|
-
savedSystem
|
|
582
|
-
});
|
|
583
|
-
useEffect(() => {
|
|
584
|
-
const seen = systemSeen.current;
|
|
585
|
-
systemSeen.current = {
|
|
586
|
-
isOpen,
|
|
587
|
-
valueKey,
|
|
588
|
-
savedSystem
|
|
589
|
-
};
|
|
590
|
-
setDraftSystem(savedSystem);
|
|
591
|
-
const systemOnly = seen.isOpen === isOpen && seen.valueKey === valueKey && seen.savedSystem !== savedSystem;
|
|
592
|
-
if (systemOnly && isOpen && edited.current) {
|
|
593
|
-
draft.clear();
|
|
594
|
-
resetSelectionUi();
|
|
595
|
-
}
|
|
596
|
-
// eslint-disable-next-line react-hooks/exhaustive-deps -- the draft's actions are stable
|
|
597
|
-
}, [isOpen, valueKey, savedSystem]);
|
|
598
|
-
useEffect(() => setDualTab(dualDefaultTab), [isOpen, dualDefaultTab]);
|
|
599
|
-
const selectionShown = draftSystem === shownSystem;
|
|
573
|
+
const hasSaved = valuesOf(saved).some(v => v !== null);
|
|
574
|
+
// Each open starts on the tab the saved value was saved with; with no value,
|
|
575
|
+
// on dualDefaultTab.
|
|
576
|
+
useEffect(() => setDualTab(hasSaved ? savedSystem : dualDefaultTab),
|
|
577
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps -- on open and on a new dualDefaultTab, not on every save
|
|
578
|
+
[isOpen, dualDefaultTab]);
|
|
600
579
|
/** Saves a value: with the calendar it was picked in, when one can be Hijri. */
|
|
601
580
|
const emitChange = useCallback((wire, picked) => {
|
|
602
581
|
savedSystemRef.current = {
|
|
@@ -620,11 +599,12 @@ const DateTimePicker = props => {
|
|
|
620
599
|
bounds: constraints.timeBounds(v.date)
|
|
621
600
|
}) === null)) && !typingBlocked && !wheelSpinning && !confirmDisabled;
|
|
622
601
|
|
|
623
|
-
// Every save: Confirm, an 'instant' edit (onConfirm is added by `confirm`)
|
|
602
|
+
// Every save: Confirm, an 'instant' edit (onConfirm is added by `confirm`),
|
|
603
|
+
// with the calendar on screen.
|
|
624
604
|
const commit = useCallback(wire => {
|
|
625
|
-
emitChange(wire,
|
|
605
|
+
emitChange(wire, shownSystem);
|
|
626
606
|
onBlur?.();
|
|
627
|
-
}, [emitChange,
|
|
607
|
+
}, [emitChange, shownSystem, onBlur]);
|
|
628
608
|
const confirm = useCallback(() => {
|
|
629
609
|
if (!canConfirm) return;
|
|
630
610
|
const {
|
|
@@ -656,8 +636,8 @@ const DateTimePicker = props => {
|
|
|
656
636
|
draft.clear();
|
|
657
637
|
resetSelectionUi();
|
|
658
638
|
edited.current = false;
|
|
659
|
-
emitChange('',
|
|
660
|
-
}, [draft, emitChange,
|
|
639
|
+
emitChange('', shownSystem);
|
|
640
|
+
}, [draft, emitChange, shownSystem, resetSelectionUi]);
|
|
661
641
|
const clearDraft = useCallback(() => {
|
|
662
642
|
edited.current = true;
|
|
663
643
|
draft.clear();
|
|
@@ -666,7 +646,6 @@ const DateTimePicker = props => {
|
|
|
666
646
|
|
|
667
647
|
// × in the field above the calendar. 'instant' saves every edit, so it
|
|
668
648
|
// clears the saved value too (once; an empty value isn't saved again).
|
|
669
|
-
const hasSaved = valuesOf(saved).some(v => v !== null);
|
|
670
649
|
const clearInSheet = useCallback(() => {
|
|
671
650
|
if (commitMode === 'instant' && hasSaved) clearValue();else clearDraft();
|
|
672
651
|
}, [commitMode, hasSaved, clearValue, clearDraft]);
|
|
@@ -678,7 +657,8 @@ const DateTimePicker = props => {
|
|
|
678
657
|
// Confirm press, but only while open (closing discards, as with Confirm),
|
|
679
658
|
// and only what the draft holds: a time still being typed isn't saved yet.
|
|
680
659
|
// An edit equal to the saved value isn't saved again; one `validate`
|
|
681
|
-
// rejected waits until the selection or the saved value changes.
|
|
660
|
+
// rejected waits until the selection or the saved value changes. A tab
|
|
661
|
+
// change the user makes saves the selection again with the new calendar.
|
|
682
662
|
const lastRejected = useRef(null);
|
|
683
663
|
// The last value saved: not saved again while the parent's `value` catches
|
|
684
664
|
// up (a slow store, a server round trip) or when it never does.
|
|
@@ -696,11 +676,12 @@ const DateTimePicker = props => {
|
|
|
696
676
|
if (!typingDone || timeIssue || !canSave(draft.draft)) return;
|
|
697
677
|
const wire = draft.toWire();
|
|
698
678
|
const key = JSON.stringify(wire);
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
// dates
|
|
679
|
+
const sent = `${shownSystem} ${key}`;
|
|
680
|
+
if (key === lastRejected.current || sent === lastEmitted.current) return;
|
|
681
|
+
// The same dates in the same calendar aren't saved again; the same dates
|
|
682
|
+
// with the other tab on screen are.
|
|
702
683
|
const unchanged = key === valueKey || contentOf(draft.draft) === savedContent;
|
|
703
|
-
if (unchanged &&
|
|
684
|
+
if (unchanged && shownSystem === savedSystem) return;
|
|
704
685
|
const message = latest.current.validate?.(wire);
|
|
705
686
|
if (message) {
|
|
706
687
|
lastRejected.current = key;
|
|
@@ -711,14 +692,14 @@ const DateTimePicker = props => {
|
|
|
711
692
|
latest.current.onError?.('VALIDATION', wire);
|
|
712
693
|
return;
|
|
713
694
|
}
|
|
714
|
-
lastEmitted.current =
|
|
695
|
+
lastEmitted.current = sent;
|
|
715
696
|
commit(wire);
|
|
716
697
|
};
|
|
717
698
|
// Only typing waits for the field to be left; a tap is a finished edit.
|
|
718
699
|
const typedLast = useRef(false);
|
|
719
700
|
useEffect(() => saveInstant(!sheetTyping || !typedLast.current),
|
|
720
701
|
// eslint-disable-next-line react-hooks/exhaustive-deps -- when the draft or what blocks it changes, not when callbacks are re-created
|
|
721
|
-
[commitMode, isOpen, sheetTyping, draft.draft,
|
|
702
|
+
[commitMode, isOpen, sheetTyping, draft.draft, shownSystem, timeIssue, typingBlocked, wheelSpinning, confirmDisabled, constraints]);
|
|
722
703
|
// Closing while the sheet's field still has focus ends the typing: an
|
|
723
704
|
// instant picker saves what was typed before it closes.
|
|
724
705
|
const saveInstantRef = useRef(saveInstant);
|
|
@@ -729,21 +710,14 @@ const DateTimePicker = props => {
|
|
|
729
710
|
}, [closePicker]);
|
|
730
711
|
|
|
731
712
|
// A typed date with no time yet gets one the way a tapped day does: the fixed
|
|
732
|
-
// newDayTime, else a time already typed in the time row, else now.
|
|
713
|
+
// newDayTime, else a time already typed in the time row, else now (or none).
|
|
733
714
|
const withFilledTime = (parsed, key) => parsed.value ?? (parsed.dateOnly ? {
|
|
734
715
|
date: parsed.dateOnly,
|
|
735
|
-
time: fixedTime ?? pending[key] ??
|
|
716
|
+
time: fixedTime ?? pending[key] ?? newTime()
|
|
736
717
|
} : null);
|
|
737
|
-
|
|
738
|
-
// Typed segments are Gregorian, so typing keeps the calendar a selection was
|
|
739
|
-
// picked in; into an empty draft, it takes the calendar on screen.
|
|
740
|
-
const adoptShownIfEmpty = () => {
|
|
741
|
-
if (valuesOf(d).every(v => v === null)) setDraftSystem(shownSystem);
|
|
742
|
-
};
|
|
743
718
|
const onSheetTyped = parsed => {
|
|
744
719
|
edited.current = true;
|
|
745
720
|
typedLast.current = true;
|
|
746
|
-
adoptShownIfEmpty();
|
|
747
721
|
const typed = withFilledTime(parsed, 'single');
|
|
748
722
|
setTypingBlock('single', !parsed.isEmpty && !typed);
|
|
749
723
|
if (typed) draft.setValue(typed, 'single');else if (parsed.isEmpty) draft.clear();
|
|
@@ -751,14 +725,13 @@ const DateTimePicker = props => {
|
|
|
751
725
|
const onRangeTyped = (end, parsed) => {
|
|
752
726
|
edited.current = true;
|
|
753
727
|
typedLast.current = true;
|
|
754
|
-
adoptShownIfEmpty();
|
|
755
728
|
const typed = withFilledTime(parsed, end);
|
|
756
729
|
setTypingBlock(end, !parsed.isEmpty && !typed);
|
|
757
730
|
if (typed || parsed.isEmpty) draft.setValue(typed, end);
|
|
758
731
|
};
|
|
759
732
|
|
|
760
733
|
// A fixed time for every newly picked day (wins over a typed time and over "now").
|
|
761
|
-
const fixedTime = newDayTime
|
|
734
|
+
const fixedTime = typeof newDayTime === 'object' ? newDayTime : null;
|
|
762
735
|
|
|
763
736
|
// A day picked on an empty date-time starts at the current time, moved up to
|
|
764
737
|
// the next allowed minute step. An existing time is kept.
|
|
@@ -772,6 +745,8 @@ const DateTimePicker = props => {
|
|
|
772
745
|
minute: capped % 60
|
|
773
746
|
};
|
|
774
747
|
}, [now, zone, minuteStep]);
|
|
748
|
+
/** A new day's time when nothing fixed or typed: now, or none with newDayTime 'empty'. */
|
|
749
|
+
const newTime = useCallback(() => startsEmpty ? null : nowTime(), [startsEmpty, nowTime]);
|
|
775
750
|
|
|
776
751
|
// A range end picked on the start's day starts an hour after the start (at
|
|
777
752
|
// most the day's latest allowed time, or its last step), so the range isn't
|
|
@@ -779,8 +754,8 @@ const DateTimePicker = props => {
|
|
|
779
754
|
// A time typed for the end before its day is kept as typed.
|
|
780
755
|
const endTimeFor = (from, day, typed) => {
|
|
781
756
|
if (!fixedTime && typed) return typed;
|
|
782
|
-
const time = fixedTime ??
|
|
783
|
-
if (!from?.time || !isSameDay(from.date, day)) return time;
|
|
757
|
+
const time = fixedTime ?? newTime();
|
|
758
|
+
if (!time || !from?.time || !isSameDay(from.date, day)) return time;
|
|
784
759
|
const start = from.time.hour * 60 + from.time.minute;
|
|
785
760
|
if (time.hour * 60 + time.minute > start) return time;
|
|
786
761
|
// Within the day's allowed hours when it has them.
|
|
@@ -795,66 +770,57 @@ const DateTimePicker = props => {
|
|
|
795
770
|
const onSelectDay = useCallback(day => {
|
|
796
771
|
edited.current = true;
|
|
797
772
|
typedLast.current = false;
|
|
798
|
-
// A
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
const cur = fresh ? emptied(d) : d;
|
|
802
|
-
const end0 = fresh ? 'from' : activeEnd;
|
|
803
|
-
const dayInEdit = fresh ? null : activeDay;
|
|
804
|
-
const times = fresh ? {} : pending;
|
|
805
|
-
if (fresh) {
|
|
806
|
-
draft.clear();
|
|
807
|
-
resetSelectionUi();
|
|
808
|
-
}
|
|
809
|
-
if (cur.mode === 'range') {
|
|
773
|
+
// A multiple picker scrolls for its first date only, so more can be picked.
|
|
774
|
+
if (d.mode !== 'multiple' || d.slots.length === 0) revealTime();
|
|
775
|
+
if (d.mode === 'range') {
|
|
810
776
|
if (rangeLayout === 'fromTo') {
|
|
811
777
|
setEndPicked(false);
|
|
812
778
|
// A full range: the tap starts a new one (or moves the nearest end).
|
|
813
|
-
if (!endPicked &&
|
|
779
|
+
if (!endPicked && d.from.value && d.to.value) {
|
|
814
780
|
const moveNearest = range?.reselect === 'moveNearestEnd';
|
|
815
|
-
const nearest = Math.abs(daysBetween(
|
|
781
|
+
const nearest = Math.abs(daysBetween(d.from.value.date, day)) < Math.abs(daysBetween(d.to.value.date, day)) ? 'from' : 'to';
|
|
816
782
|
const end = moveNearest ? nearest : 'from';
|
|
817
783
|
setTypingBlock('from', false);
|
|
818
784
|
setTypingBlock('to', false);
|
|
819
|
-
draft.selectDay(day, fixedTime ??
|
|
785
|
+
draft.selectDay(day, fixedTime ?? pending[end] ?? newTime());
|
|
820
786
|
setActiveEnd(moveNearest ? nearest : 'to');
|
|
821
787
|
return;
|
|
822
788
|
}
|
|
823
789
|
// Choosing the end after a start tap: a day before the start moves the start.
|
|
824
|
-
const end1 =
|
|
790
|
+
const end1 = activeEnd === 'to' && !endPicked && d.from.value && compareCalendarDates(day, d.from.value.date) < 0 ? 'from' : activeEnd;
|
|
825
791
|
// The tapped day replaces whatever was typed in this end only.
|
|
826
792
|
setTypingBlock(end1, false);
|
|
827
|
-
draft.setRangeEnd(day, end1, end1 === 'to' ? endTimeFor(
|
|
793
|
+
draft.setRangeEnd(day, end1, end1 === 'to' ? endTimeFor(d.from.value, day, pending.to) : fixedTime ?? pending.from ?? newTime());
|
|
828
794
|
// The start tapped again where a range can't be one day deselects
|
|
829
795
|
// it, so the next tap picks the start again.
|
|
830
|
-
const deselects = end1 === 'to' && !sameDayRangeOk(draftOptions) && isSameDay(
|
|
831
|
-
if (
|
|
796
|
+
const deselects = end1 === 'to' && !sameDayRangeOk(draftOptions) && isSameDay(d.from.value?.date, day);
|
|
797
|
+
if (activeEnd === 'from') setActiveEnd('to');else if (deselects) setActiveEnd('from');
|
|
832
798
|
return;
|
|
833
799
|
}
|
|
834
800
|
// The single field: the first tap is the start, the next one the end.
|
|
835
|
-
const startsOver = !
|
|
801
|
+
const startsOver = !d.from.value || !!d.to.value && range?.reselect !== 'moveNearestEnd';
|
|
836
802
|
const end = startsOver ? 'from' : 'to';
|
|
837
803
|
setTypingBlock('single', false);
|
|
838
|
-
draft.selectDay(day, end === 'to' && !
|
|
804
|
+
draft.selectDay(day, end === 'to' && !d.to.value ? endTimeFor(d.from.value, day, pending.to) : fixedTime ?? pending[end] ?? newTime());
|
|
839
805
|
return;
|
|
840
806
|
}
|
|
841
|
-
if (
|
|
807
|
+
if (d.mode === 'multiple') {
|
|
842
808
|
const perDate = type === 'datetime' && multipleTimeMode === 'perDate';
|
|
843
|
-
const picked =
|
|
809
|
+
const picked = d.slots.some(slot => isSameDay(slot.value?.date, day));
|
|
844
810
|
// perDate: a tap on a picked day first makes it the one the time row edits.
|
|
845
|
-
if (perDate && picked && !isSameDay(
|
|
811
|
+
if (perDate && picked && !isSameDay(activeDay, day)) {
|
|
846
812
|
setActiveDay(day);
|
|
847
813
|
return;
|
|
848
814
|
}
|
|
849
|
-
draft.selectDay(day, fixedTime ??
|
|
815
|
+
draft.selectDay(day, fixedTime ?? newTime());
|
|
850
816
|
if (perDate) setActiveDay(picked ? null : day);
|
|
851
817
|
return;
|
|
852
818
|
}
|
|
853
819
|
setTypingBlock('single', false);
|
|
854
|
-
draft.selectDay(day, fixedTime ??
|
|
820
|
+
draft.selectDay(day, fixedTime ?? pending.single ?? newTime());
|
|
855
821
|
},
|
|
856
822
|
// eslint-disable-next-line react-hooks/exhaustive-deps -- valuesOf reads only `d` and props listed here
|
|
857
|
-
[d, draft,
|
|
823
|
+
[d, draft, newTime, rangeLayout, activeEnd, activeDay, fixedTime, pending, range?.reselect, type, multipleTimeMode, setTypingBlock, endPicked, draftOptions, minuteStep, constraints, revealTime]);
|
|
858
824
|
|
|
859
825
|
// The calendar gets a tap handler that never changes, so its memoized day
|
|
860
826
|
// cells don't all redraw on every tap; it calls the current one.
|
|
@@ -974,12 +940,13 @@ const DateTimePicker = props => {
|
|
|
974
940
|
|
|
975
941
|
/** A value's date in the calendar it was picked in: Hijri text, or none for the dateFormat. */
|
|
976
942
|
const dateTextFor = picked => picked === 'hijri' ? v => hijriDateText(v.date, hijriLocale) ?? formatDateText(v, dateFormat) : undefined;
|
|
977
|
-
|
|
978
|
-
const
|
|
979
|
-
const
|
|
980
|
-
const
|
|
981
|
-
|
|
982
|
-
//
|
|
943
|
+
// The calendar the on-screen field writes the saved value in.
|
|
944
|
+
const fieldSystem = fieldCalendar === 'gregory' ? 'gregory' : savedSystem;
|
|
945
|
+
const savedDateText = dateTextFor(fieldSystem);
|
|
946
|
+
const draftDateText = dateTextFor(shownSystem);
|
|
947
|
+
const savedStrings = stringsFor(fieldSystem);
|
|
948
|
+
// The texts inside the picker, its error messages too, follow the tab on
|
|
949
|
+
// screen; the on-screen field's keep `strings`.
|
|
983
950
|
const shownStrings = stringsFor(shownSystem);
|
|
984
951
|
|
|
985
952
|
// What the on-screen field shows: the saved value, never the draft.
|
|
@@ -1010,7 +977,7 @@ const DateTimePicker = props => {
|
|
|
1010
977
|
type,
|
|
1011
978
|
mode,
|
|
1012
979
|
value,
|
|
1013
|
-
displayText: digitsFor(
|
|
980
|
+
displayText: digitsFor(fieldSystem)(savedText ?? formatValueText(savedSingle, type, dateFormat, timeFormat, savedDateText, savedStrings)),
|
|
1014
981
|
disabled,
|
|
1015
982
|
readOnly,
|
|
1016
983
|
isOpen,
|
|
@@ -1031,11 +998,12 @@ const DateTimePicker = props => {
|
|
|
1031
998
|
};
|
|
1032
999
|
const sheetValue = draft.draft.mode === 'single' ? draft.draft.slot.value : null;
|
|
1033
1000
|
// A time outside the limits is explained under the time row, not here.
|
|
1034
|
-
const
|
|
1001
|
+
const notAllowedError = sheetOptions?.showNotAllowedError === false ? '' : shownStrings.errorNotAllowed;
|
|
1002
|
+
const sheetValueError = sheetValue && dayOff(sheetValue.date) ? notAllowedError : '';
|
|
1035
1003
|
let rangeError = '';
|
|
1036
1004
|
if (d.mode === 'range' && d.from.value && d.to.value) {
|
|
1037
1005
|
const length = daysBetween(d.from.value.date, d.to.value.date) + 1;
|
|
1038
|
-
if (constraints.dayStatus(d.from.value.date).disabled || constraints.dayStatus(d.to.value.date).disabled) rangeError =
|
|
1006
|
+
if (constraints.dayStatus(d.from.value.date).disabled || constraints.dayStatus(d.to.value.date).disabled) rangeError = notAllowedError;else if (previewIssue === 'RANGE_ORDER') rangeError = shownStrings.errorRangeOrder;else if (previewIssue === 'RANGE_LENGTH') rangeError = range?.minDays !== undefined && length < range.minDays ? shownStrings.errorRangeTooShort(range.minDays) : shownStrings.errorRangeTooLong(range?.maxDays ?? length);
|
|
1039
1007
|
}
|
|
1040
1008
|
const fieldCommon = {
|
|
1041
1009
|
type,
|
|
@@ -1085,7 +1053,7 @@ const DateTimePicker = props => {
|
|
|
1085
1053
|
testID: testID ? `${testID}-value` : undefined
|
|
1086
1054
|
}) : mode === 'single' ? /*#__PURE__*/_jsx(ValueField, {
|
|
1087
1055
|
...fieldCommon,
|
|
1088
|
-
strings:
|
|
1056
|
+
strings: shownStrings,
|
|
1089
1057
|
errorStrings: shownStrings,
|
|
1090
1058
|
value: sheetValue,
|
|
1091
1059
|
displayText: precision !== 'day' ? periodText(sheetValue) : undefined,
|
|
@@ -1102,7 +1070,7 @@ const DateTimePicker = props => {
|
|
|
1102
1070
|
testID: testID ? `${testID}-value` : undefined
|
|
1103
1071
|
}) : d.mode === 'range' ? /*#__PURE__*/_jsx(RangeFields, {
|
|
1104
1072
|
...fieldCommon,
|
|
1105
|
-
strings:
|
|
1073
|
+
strings: shownStrings,
|
|
1106
1074
|
errorStrings: shownStrings,
|
|
1107
1075
|
layout: rangeLayout,
|
|
1108
1076
|
dateText: draftDateText,
|
|
@@ -1122,9 +1090,9 @@ const DateTimePicker = props => {
|
|
|
1122
1090
|
testID: testID
|
|
1123
1091
|
}) : d.mode === 'multiple' ? /*#__PURE__*/_jsx(ValueField, {
|
|
1124
1092
|
...fieldCommon,
|
|
1125
|
-
strings:
|
|
1093
|
+
strings: shownStrings,
|
|
1126
1094
|
value: null,
|
|
1127
|
-
displayText: formatMultipleText(draftValues, type, dateFormat, timeFormat, draftDateText,
|
|
1095
|
+
displayText: formatMultipleText(draftValues, type, dateFormat, timeFormat, draftDateText, shownStrings),
|
|
1128
1096
|
placeholder: sheetOptions?.fieldPlaceholder,
|
|
1129
1097
|
allowClear: allowClear,
|
|
1130
1098
|
onClear: clearInSheet,
|
|
@@ -1139,13 +1107,20 @@ const DateTimePicker = props => {
|
|
|
1139
1107
|
dual: strings.calendarDual ?? DEFAULT_STRINGS.calendarDual
|
|
1140
1108
|
};
|
|
1141
1109
|
const systemA11yLabel = strings.a11yCalendarSystem ?? DEFAULT_STRINGS.a11yCalendarSystem;
|
|
1110
|
+
// A calendar or tab the user picks is an edit: 'instant' saves the selection
|
|
1111
|
+
// again with that calendar.
|
|
1142
1112
|
const chooseSystem = next => {
|
|
1143
1113
|
if (next === system) return;
|
|
1114
|
+
edited.current = true;
|
|
1144
1115
|
setChosenSystem(next);
|
|
1145
1116
|
if (next === 'dual') setDualTab(dualDefaultTab);
|
|
1146
1117
|
echo.current = next;
|
|
1147
1118
|
onCalendarSystemChange?.(next);
|
|
1148
1119
|
};
|
|
1120
|
+
const chooseTab = next => {
|
|
1121
|
+
edited.current = true;
|
|
1122
|
+
setDualTab(next);
|
|
1123
|
+
};
|
|
1149
1124
|
const calendarSwitchNode = !switchSystems ? null : renderCalendarSwitch ? renderCalendarSwitch({
|
|
1150
1125
|
systems: switchSystems,
|
|
1151
1126
|
selected: system,
|
|
@@ -1177,7 +1152,7 @@ const DateTimePicker = props => {
|
|
|
1177
1152
|
label: systemLabels.hijri
|
|
1178
1153
|
}],
|
|
1179
1154
|
selected: dualTab,
|
|
1180
|
-
onSelect:
|
|
1155
|
+
onSelect: chooseTab,
|
|
1181
1156
|
accessibilityLabel: systemA11yLabel,
|
|
1182
1157
|
tokens: tokens,
|
|
1183
1158
|
styles: styles,
|
|
@@ -1193,16 +1168,16 @@ const DateTimePicker = props => {
|
|
|
1193
1168
|
children: node
|
|
1194
1169
|
})
|
|
1195
1170
|
});
|
|
1196
|
-
// Another calendar opens on the days the last one showed, else
|
|
1197
|
-
//
|
|
1198
|
-
const calendarAnchor = shownAnchor.current ??
|
|
1171
|
+
// Another calendar opens on the days the last one showed, else where the
|
|
1172
|
+
// picker would open.
|
|
1173
|
+
const calendarAnchor = shownAnchor.current ?? initialVisibleDate;
|
|
1199
1174
|
const calendar = /*#__PURE__*/_jsxs(View, {
|
|
1200
1175
|
style: folded ? FOLDED : undefined,
|
|
1201
1176
|
children: [calendarSwitchNode, dualTabs, mirrored(/*#__PURE__*/_jsx(Calendar, {
|
|
1202
|
-
constraints:
|
|
1177
|
+
constraints: calendarConstraints,
|
|
1203
1178
|
today: today,
|
|
1204
1179
|
initialVisibleDate: calendarAnchor,
|
|
1205
|
-
selection:
|
|
1180
|
+
selection: selection,
|
|
1206
1181
|
onSelectDay: onSelectDayStable,
|
|
1207
1182
|
firstDayOfWeek: firstDayOfWeek,
|
|
1208
1183
|
showOutsideDays: showOutsideDays,
|
|
@@ -1265,18 +1240,22 @@ const DateTimePicker = props => {
|
|
|
1265
1240
|
// Shown while the selection is still the one it was given for.
|
|
1266
1241
|
const validationMessage = validation && (validation.key === JSON.stringify(draft.toWire()) || validation.key === JSON.stringify(draftToWire(preview, draftOptions))) ? validation.message : null;
|
|
1267
1242
|
|
|
1268
|
-
// The field
|
|
1269
|
-
// the calendar and the time rows follow the tab that shows.
|
|
1243
|
+
// The field, the calendar and the time rows follow the tab that shows.
|
|
1270
1244
|
const sheetContent = /*#__PURE__*/_jsxs(_Fragment, {
|
|
1271
|
-
children: [/*#__PURE__*/
|
|
1272
|
-
value: digitsFor(draftSystem),
|
|
1273
|
-
children: sheetField
|
|
1274
|
-
}), /*#__PURE__*/_jsx(NumeralsContext.Provider, {
|
|
1275
|
-
value: digitsFor(shownSystem),
|
|
1276
|
-
children: calendar
|
|
1277
|
-
}), /*#__PURE__*/_jsx(NumeralsContext.Provider, {
|
|
1245
|
+
children: [/*#__PURE__*/_jsxs(NumeralsContext.Provider, {
|
|
1278
1246
|
value: digitsFor(shownSystem),
|
|
1279
|
-
children:
|
|
1247
|
+
children: [sheetField, calendar]
|
|
1248
|
+
}), timeRow == null ? null :
|
|
1249
|
+
/*#__PURE__*/
|
|
1250
|
+
// Measured for the scroll to the time (sheet.scrollToTime).
|
|
1251
|
+
_jsx(View, {
|
|
1252
|
+
ref: timeRef,
|
|
1253
|
+
collapsable: false,
|
|
1254
|
+
testID: testID ? `${testID}-time-section` : undefined,
|
|
1255
|
+
children: /*#__PURE__*/_jsx(NumeralsContext.Provider, {
|
|
1256
|
+
value: digitsFor(shownSystem),
|
|
1257
|
+
children: mirrored(timeRow)
|
|
1258
|
+
})
|
|
1280
1259
|
}), validationMessage ? /*#__PURE__*/_jsx(FieldMessages, {
|
|
1281
1260
|
error: validationMessage,
|
|
1282
1261
|
tokens: tokens,
|
|
@@ -1288,13 +1267,14 @@ const DateTimePicker = props => {
|
|
|
1288
1267
|
}) : null]
|
|
1289
1268
|
});
|
|
1290
1269
|
return /*#__PURE__*/_jsxs(NumeralsContext.Provider, {
|
|
1291
|
-
value: digitsFor(
|
|
1270
|
+
value: digitsFor(fieldSystem),
|
|
1292
1271
|
children: [trigger, /*#__PURE__*/_jsx(PickerContainer, {
|
|
1293
1272
|
presentation: presentation,
|
|
1294
1273
|
isOpen: isOpen,
|
|
1295
1274
|
onRequestClose: close,
|
|
1296
1275
|
renderSheet: renderSheet,
|
|
1297
1276
|
sheet: sheetOptions,
|
|
1277
|
+
scrollRef: scrollRef,
|
|
1298
1278
|
state: state,
|
|
1299
1279
|
header: header,
|
|
1300
1280
|
title: title ?? (isInline ? undefined : strings.titleDate),
|