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
|
@@ -15,7 +15,6 @@ import {
|
|
|
15
15
|
} from '../core/calendarDate';
|
|
16
16
|
import { createConstraints } from '../core/constraints';
|
|
17
17
|
import {
|
|
18
|
-
clearDraft as emptied,
|
|
19
18
|
createDraft,
|
|
20
19
|
DEFAULT_TIME,
|
|
21
20
|
draftToWire,
|
|
@@ -56,7 +55,7 @@ import CalendarSwitch from './calendar/CalendarSwitch';
|
|
|
56
55
|
import { localeNames } from './calendar/localeNames';
|
|
57
56
|
import { RtlContext } from './theme/direction';
|
|
58
57
|
import { NumeralsContext } from './theme/numerals';
|
|
59
|
-
import {
|
|
58
|
+
import type { CalendarSelection } from './calendar/selection';
|
|
60
59
|
import PickerContainer from './container/PickerContainer';
|
|
61
60
|
import {
|
|
62
61
|
formatDateText,
|
|
@@ -70,6 +69,7 @@ import RangeFields, { type RangeEnd } from './field/RangeFields';
|
|
|
70
69
|
import ValueField from './field/ValueField';
|
|
71
70
|
import { useKeyboardFold } from './hooks/useKeyboardFold';
|
|
72
71
|
import { useOpenPicker } from './hooks/useOpenPicker';
|
|
72
|
+
import { useScrollToTime } from './hooks/useScrollToTime';
|
|
73
73
|
import { usePickerDraft } from './hooks/usePickerDraft';
|
|
74
74
|
import { useTodayTick } from './hooks/useTodayTick';
|
|
75
75
|
import TimeRows, { type PendingTimes } from './time/TimeRows';
|
|
@@ -94,14 +94,6 @@ const sameValue = (a: DateTimeValue, b: DateTimeValue): boolean =>
|
|
|
94
94
|
const ALL_SYSTEMS: CalendarSystem[] = ['gregory', 'hijri', 'dual'];
|
|
95
95
|
const RTL = { direction: 'rtl' } as const;
|
|
96
96
|
|
|
97
|
-
/** The same kind of selection with nothing in it. */
|
|
98
|
-
const emptySelection = (sel: CalendarSelection): CalendarSelection =>
|
|
99
|
-
sel.kind === 'single'
|
|
100
|
-
? { kind: 'single', day: null }
|
|
101
|
-
: sel.kind === 'range'
|
|
102
|
-
? { kind: 'range', from: null, to: null }
|
|
103
|
-
: { ...sel, days: [] };
|
|
104
|
-
|
|
105
97
|
/** Minute steps that divide the hour. */
|
|
106
98
|
const MINUTE_STEPS = [1, 2, 3, 4, 5, 6, 10, 12, 15, 20, 30, 60];
|
|
107
99
|
|
|
@@ -138,6 +130,7 @@ const DateTimePicker: React.FC<DateTimePickerProps> = (props) => {
|
|
|
138
130
|
multiple,
|
|
139
131
|
range,
|
|
140
132
|
presentation = 'sheet',
|
|
133
|
+
scrollToTimeRef,
|
|
141
134
|
visible,
|
|
142
135
|
onOpen,
|
|
143
136
|
onClose,
|
|
@@ -188,6 +181,7 @@ const DateTimePicker: React.FC<DateTimePickerProps> = (props) => {
|
|
|
188
181
|
calendarSwitch,
|
|
189
182
|
onCalendarSystemChange,
|
|
190
183
|
dualDefaultTab = 'gregory',
|
|
184
|
+
fieldCalendar = 'picked',
|
|
191
185
|
hijriLocale = 'ar-SA',
|
|
192
186
|
renderCalendarSwitch,
|
|
193
187
|
showToday = false,
|
|
@@ -432,12 +426,13 @@ const DateTimePicker: React.FC<DateTimePickerProps> = (props) => {
|
|
|
432
426
|
|
|
433
427
|
const rangeLayout = range?.layout ?? 'singleField';
|
|
434
428
|
const multipleTimeMode = multiple?.timeMode ?? 'shared';
|
|
429
|
+
const startsEmpty = newDayTime === 'empty';
|
|
435
430
|
const draftOptions = useMemo((): DraftOptions => {
|
|
436
431
|
return {
|
|
437
432
|
type,
|
|
438
433
|
zone,
|
|
439
434
|
adapter,
|
|
440
|
-
defaultTime: DEFAULT_TIME,
|
|
435
|
+
defaultTime: startsEmpty ? null : DEFAULT_TIME,
|
|
441
436
|
multiple: {
|
|
442
437
|
timeMode: multiple?.timeMode,
|
|
443
438
|
maxSelections: multiple?.maxSelections,
|
|
@@ -453,6 +448,7 @@ const DateTimePicker: React.FC<DateTimePickerProps> = (props) => {
|
|
|
453
448
|
type,
|
|
454
449
|
zone,
|
|
455
450
|
adapter,
|
|
451
|
+
startsEmpty,
|
|
456
452
|
multiple?.timeMode,
|
|
457
453
|
multiple?.maxSelections,
|
|
458
454
|
range?.allowSameDay,
|
|
@@ -672,6 +668,28 @@ const DateTimePicker: React.FC<DateTimePickerProps> = (props) => {
|
|
|
672
668
|
const previewIssue = validateDraft(preview, draftOptions);
|
|
673
669
|
|
|
674
670
|
const d = draft.draft;
|
|
671
|
+
// Once a calendar tap picks the date (both ends of a range), the sheet (or
|
|
672
|
+
// the app's ScrollView around an inline picker) scrolls the time section
|
|
673
|
+
// into view.
|
|
674
|
+
const datePicked =
|
|
675
|
+
d.mode === 'single'
|
|
676
|
+
? !!d.slot.value
|
|
677
|
+
: d.mode === 'range'
|
|
678
|
+
? !!d.from.value && !!d.to.value
|
|
679
|
+
: d.slots.length > 0;
|
|
680
|
+
const {
|
|
681
|
+
scrollRef,
|
|
682
|
+
timeRef,
|
|
683
|
+
request: revealTime,
|
|
684
|
+
} = useScrollToTime(
|
|
685
|
+
type !== 'datetime'
|
|
686
|
+
? false
|
|
687
|
+
: isInline
|
|
688
|
+
? !!scrollToTimeRef
|
|
689
|
+
: (sheetOptions?.scrollToTime ?? true),
|
|
690
|
+
datePicked,
|
|
691
|
+
isInline ? scrollToTimeRef : undefined
|
|
692
|
+
);
|
|
675
693
|
/** The values a draft would save, with a shared time applied. */
|
|
676
694
|
const valuesOf = (dr: Draft): (DateTimeValue | null)[] => {
|
|
677
695
|
if (dr.mode === 'single') return [dr.slot.value];
|
|
@@ -714,10 +732,10 @@ const DateTimePicker: React.FC<DateTimePickerProps> = (props) => {
|
|
|
714
732
|
latest.current = { validate, onError, value };
|
|
715
733
|
const valueKey = JSON.stringify(value ?? '');
|
|
716
734
|
|
|
717
|
-
// A selection
|
|
718
|
-
//
|
|
719
|
-
// value remembers its calendar; one from outside counts as picked in
|
|
720
|
-
// calendar the picker opens on.
|
|
735
|
+
// A selection shows in both tabs (Gregorian and Hijri). It is saved with the
|
|
736
|
+
// calendar on screen, and the on-screen field writes it in that calendar. A
|
|
737
|
+
// saved value remembers its calendar; one from outside counts as picked in
|
|
738
|
+
// the calendar the picker opens on.
|
|
721
739
|
// The saved value as a draft, for the on-screen field and for comparing
|
|
722
740
|
// values by what they hold.
|
|
723
741
|
const saved = useMemo(
|
|
@@ -736,27 +754,14 @@ const DateTimePicker: React.FC<DateTimePickerProps> = (props) => {
|
|
|
736
754
|
savedSystemRef.current?.content === savedContent
|
|
737
755
|
? savedSystemRef.current.system
|
|
738
756
|
: outsideSystem;
|
|
739
|
-
const
|
|
740
|
-
//
|
|
741
|
-
//
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
setDraftSystem(savedSystem);
|
|
748
|
-
const systemOnly =
|
|
749
|
-
seen.isOpen === isOpen &&
|
|
750
|
-
seen.valueKey === valueKey &&
|
|
751
|
-
seen.savedSystem !== savedSystem;
|
|
752
|
-
if (systemOnly && isOpen && edited.current) {
|
|
753
|
-
draft.clear();
|
|
754
|
-
resetSelectionUi();
|
|
755
|
-
}
|
|
756
|
-
// eslint-disable-next-line react-hooks/exhaustive-deps -- the draft's actions are stable
|
|
757
|
-
}, [isOpen, valueKey, savedSystem]);
|
|
758
|
-
useEffect(() => setDualTab(dualDefaultTab), [isOpen, dualDefaultTab]);
|
|
759
|
-
const selectionShown = draftSystem === shownSystem;
|
|
757
|
+
const hasSaved = valuesOf(saved).some((v) => v !== null);
|
|
758
|
+
// Each open starts on the tab the saved value was saved with; with no value,
|
|
759
|
+
// on dualDefaultTab.
|
|
760
|
+
useEffect(
|
|
761
|
+
() => setDualTab(hasSaved ? savedSystem : dualDefaultTab),
|
|
762
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps -- on open and on a new dualDefaultTab, not on every save
|
|
763
|
+
[isOpen, dualDefaultTab]
|
|
764
|
+
);
|
|
760
765
|
/** Saves a value: with the calendar it was picked in, when one can be Hijri. */
|
|
761
766
|
const emitChange = useCallback(
|
|
762
767
|
(wire: PickerValue, picked: 'gregory' | 'hijri') => {
|
|
@@ -794,13 +799,14 @@ const DateTimePicker: React.FC<DateTimePickerProps> = (props) => {
|
|
|
794
799
|
!wheelSpinning &&
|
|
795
800
|
!confirmDisabled;
|
|
796
801
|
|
|
797
|
-
// Every save: Confirm, an 'instant' edit (onConfirm is added by `confirm`)
|
|
802
|
+
// Every save: Confirm, an 'instant' edit (onConfirm is added by `confirm`),
|
|
803
|
+
// with the calendar on screen.
|
|
798
804
|
const commit = useCallback(
|
|
799
805
|
(wire: PickerValue) => {
|
|
800
|
-
emitChange(wire,
|
|
806
|
+
emitChange(wire, shownSystem);
|
|
801
807
|
onBlur?.();
|
|
802
808
|
},
|
|
803
|
-
[emitChange,
|
|
809
|
+
[emitChange, shownSystem, onBlur]
|
|
804
810
|
);
|
|
805
811
|
|
|
806
812
|
const confirm = useCallback(() => {
|
|
@@ -838,8 +844,8 @@ const DateTimePicker: React.FC<DateTimePickerProps> = (props) => {
|
|
|
838
844
|
draft.clear();
|
|
839
845
|
resetSelectionUi();
|
|
840
846
|
edited.current = false;
|
|
841
|
-
emitChange('',
|
|
842
|
-
}, [draft, emitChange,
|
|
847
|
+
emitChange('', shownSystem);
|
|
848
|
+
}, [draft, emitChange, shownSystem, resetSelectionUi]);
|
|
843
849
|
|
|
844
850
|
const clearDraft = useCallback(() => {
|
|
845
851
|
edited.current = true;
|
|
@@ -849,7 +855,6 @@ const DateTimePicker: React.FC<DateTimePickerProps> = (props) => {
|
|
|
849
855
|
|
|
850
856
|
// × in the field above the calendar. 'instant' saves every edit, so it
|
|
851
857
|
// clears the saved value too (once; an empty value isn't saved again).
|
|
852
|
-
const hasSaved = valuesOf(saved).some((v) => v !== null);
|
|
853
858
|
const clearInSheet = useCallback(() => {
|
|
854
859
|
if (commitMode === 'instant' && hasSaved) clearValue();
|
|
855
860
|
else clearDraft();
|
|
@@ -862,7 +867,8 @@ const DateTimePicker: React.FC<DateTimePickerProps> = (props) => {
|
|
|
862
867
|
// Confirm press, but only while open (closing discards, as with Confirm),
|
|
863
868
|
// and only what the draft holds: a time still being typed isn't saved yet.
|
|
864
869
|
// An edit equal to the saved value isn't saved again; one `validate`
|
|
865
|
-
// rejected waits until the selection or the saved value changes.
|
|
870
|
+
// rejected waits until the selection or the saved value changes. A tab
|
|
871
|
+
// change the user makes saves the selection again with the new calendar.
|
|
866
872
|
const lastRejected = useRef<string | null>(null);
|
|
867
873
|
// The last value saved: not saved again while the parent's `value` catches
|
|
868
874
|
// up (a slow store, a server round trip) or when it never does.
|
|
@@ -884,12 +890,13 @@ const DateTimePicker: React.FC<DateTimePickerProps> = (props) => {
|
|
|
884
890
|
if (!typingDone || timeIssue || !canSave(draft.draft)) return;
|
|
885
891
|
const wire = draft.toWire();
|
|
886
892
|
const key = JSON.stringify(wire);
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
// dates
|
|
893
|
+
const sent = `${shownSystem} ${key}`;
|
|
894
|
+
if (key === lastRejected.current || sent === lastEmitted.current) return;
|
|
895
|
+
// The same dates in the same calendar aren't saved again; the same dates
|
|
896
|
+
// with the other tab on screen are.
|
|
890
897
|
const unchanged =
|
|
891
898
|
key === valueKey || contentOf(draft.draft) === savedContent;
|
|
892
|
-
if (unchanged &&
|
|
899
|
+
if (unchanged && shownSystem === savedSystem) return;
|
|
893
900
|
const message = latest.current.validate?.(wire);
|
|
894
901
|
if (message) {
|
|
895
902
|
lastRejected.current = key;
|
|
@@ -897,7 +904,7 @@ const DateTimePicker: React.FC<DateTimePickerProps> = (props) => {
|
|
|
897
904
|
latest.current.onError?.('VALIDATION', wire);
|
|
898
905
|
return;
|
|
899
906
|
}
|
|
900
|
-
lastEmitted.current =
|
|
907
|
+
lastEmitted.current = sent;
|
|
901
908
|
commit(wire);
|
|
902
909
|
};
|
|
903
910
|
// Only typing waits for the field to be left; a tap is a finished edit.
|
|
@@ -910,7 +917,7 @@ const DateTimePicker: React.FC<DateTimePickerProps> = (props) => {
|
|
|
910
917
|
isOpen,
|
|
911
918
|
sheetTyping,
|
|
912
919
|
draft.draft,
|
|
913
|
-
|
|
920
|
+
shownSystem,
|
|
914
921
|
timeIssue,
|
|
915
922
|
typingBlocked,
|
|
916
923
|
wheelSpinning,
|
|
@@ -928,7 +935,7 @@ const DateTimePicker: React.FC<DateTimePickerProps> = (props) => {
|
|
|
928
935
|
}, [closePicker]);
|
|
929
936
|
|
|
930
937
|
// A typed date with no time yet gets one the way a tapped day does: the fixed
|
|
931
|
-
// newDayTime, else a time already typed in the time row, else now.
|
|
938
|
+
// newDayTime, else a time already typed in the time row, else now (or none).
|
|
932
939
|
const withFilledTime = (
|
|
933
940
|
parsed: ParsedSegments,
|
|
934
941
|
key: 'single' | RangeEnd
|
|
@@ -937,20 +944,13 @@ const DateTimePicker: React.FC<DateTimePickerProps> = (props) => {
|
|
|
937
944
|
(parsed.dateOnly
|
|
938
945
|
? {
|
|
939
946
|
date: parsed.dateOnly,
|
|
940
|
-
time: fixedTime ?? pending[key] ??
|
|
947
|
+
time: fixedTime ?? pending[key] ?? newTime(),
|
|
941
948
|
}
|
|
942
949
|
: null);
|
|
943
950
|
|
|
944
|
-
// Typed segments are Gregorian, so typing keeps the calendar a selection was
|
|
945
|
-
// picked in; into an empty draft, it takes the calendar on screen.
|
|
946
|
-
const adoptShownIfEmpty = () => {
|
|
947
|
-
if (valuesOf(d).every((v) => v === null)) setDraftSystem(shownSystem);
|
|
948
|
-
};
|
|
949
|
-
|
|
950
951
|
const onSheetTyped = (parsed: ParsedSegments) => {
|
|
951
952
|
edited.current = true;
|
|
952
953
|
typedLast.current = true;
|
|
953
|
-
adoptShownIfEmpty();
|
|
954
954
|
const typed = withFilledTime(parsed, 'single');
|
|
955
955
|
setTypingBlock('single', !parsed.isEmpty && !typed);
|
|
956
956
|
if (typed) draft.setValue(typed, 'single');
|
|
@@ -960,14 +960,13 @@ const DateTimePicker: React.FC<DateTimePickerProps> = (props) => {
|
|
|
960
960
|
const onRangeTyped = (end: RangeEnd, parsed: ParsedSegments) => {
|
|
961
961
|
edited.current = true;
|
|
962
962
|
typedLast.current = true;
|
|
963
|
-
adoptShownIfEmpty();
|
|
964
963
|
const typed = withFilledTime(parsed, end);
|
|
965
964
|
setTypingBlock(end, !parsed.isEmpty && !typed);
|
|
966
965
|
if (typed || parsed.isEmpty) draft.setValue(typed, end);
|
|
967
966
|
};
|
|
968
967
|
|
|
969
968
|
// A fixed time for every newly picked day (wins over a typed time and over "now").
|
|
970
|
-
const fixedTime = newDayTime
|
|
969
|
+
const fixedTime = typeof newDayTime === 'object' ? newDayTime : null;
|
|
971
970
|
|
|
972
971
|
// A day picked on an empty date-time starts at the current time, moved up to
|
|
973
972
|
// the next allowed minute step. An existing time is kept.
|
|
@@ -978,6 +977,11 @@ const DateTimePicker: React.FC<DateTimePickerProps> = (props) => {
|
|
|
978
977
|
const capped = Math.min(minutes, 23 * 60 + 60 - step);
|
|
979
978
|
return { hour: Math.floor(capped / 60), minute: capped % 60 };
|
|
980
979
|
}, [now, zone, minuteStep]);
|
|
980
|
+
/** A new day's time when nothing fixed or typed: now, or none with newDayTime 'empty'. */
|
|
981
|
+
const newTime = useCallback(
|
|
982
|
+
(): Time | null => (startsEmpty ? null : nowTime()),
|
|
983
|
+
[startsEmpty, nowTime]
|
|
984
|
+
);
|
|
981
985
|
|
|
982
986
|
// A range end picked on the start's day starts an hour after the start (at
|
|
983
987
|
// most the day's latest allowed time, or its last step), so the range isn't
|
|
@@ -987,10 +991,10 @@ const DateTimePicker: React.FC<DateTimePickerProps> = (props) => {
|
|
|
987
991
|
from: DateTimeValue | null,
|
|
988
992
|
day: CalendarDate,
|
|
989
993
|
typed: Time | null | undefined
|
|
990
|
-
): Time => {
|
|
994
|
+
): Time | null => {
|
|
991
995
|
if (!fixedTime && typed) return typed;
|
|
992
|
-
const time = fixedTime ??
|
|
993
|
-
if (!from?.time || !isSameDay(from.date, day)) return time;
|
|
996
|
+
const time = fixedTime ?? newTime();
|
|
997
|
+
if (!time || !from?.time || !isSameDay(from.date, day)) return time;
|
|
994
998
|
const start = from.time.hour * 60 + from.time.minute;
|
|
995
999
|
if (time.hour * 60 + time.minute > start) return time;
|
|
996
1000
|
// Within the day's allowed hours when it has them.
|
|
@@ -1006,99 +1010,87 @@ const DateTimePicker: React.FC<DateTimePickerProps> = (props) => {
|
|
|
1006
1010
|
(day: CalendarDate) => {
|
|
1007
1011
|
edited.current = true;
|
|
1008
1012
|
typedLast.current = false;
|
|
1009
|
-
// A
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
setDraftSystem(shownSystem);
|
|
1013
|
-
const cur = fresh ? emptied(d) : d;
|
|
1014
|
-
const end0: RangeEnd = fresh ? 'from' : activeEnd;
|
|
1015
|
-
const dayInEdit = fresh ? null : activeDay;
|
|
1016
|
-
const times: PendingTimes = fresh ? {} : pending;
|
|
1017
|
-
if (fresh) {
|
|
1018
|
-
draft.clear();
|
|
1019
|
-
resetSelectionUi();
|
|
1020
|
-
}
|
|
1021
|
-
if (cur.mode === 'range') {
|
|
1013
|
+
// A multiple picker scrolls for its first date only, so more can be picked.
|
|
1014
|
+
if (d.mode !== 'multiple' || d.slots.length === 0) revealTime();
|
|
1015
|
+
if (d.mode === 'range') {
|
|
1022
1016
|
if (rangeLayout === 'fromTo') {
|
|
1023
1017
|
setEndPicked(false);
|
|
1024
1018
|
// A full range: the tap starts a new one (or moves the nearest end).
|
|
1025
|
-
if (!endPicked &&
|
|
1019
|
+
if (!endPicked && d.from.value && d.to.value) {
|
|
1026
1020
|
const moveNearest = range?.reselect === 'moveNearestEnd';
|
|
1027
1021
|
const nearest: RangeEnd =
|
|
1028
|
-
Math.abs(daysBetween(
|
|
1029
|
-
Math.abs(daysBetween(
|
|
1022
|
+
Math.abs(daysBetween(d.from.value.date, day)) <
|
|
1023
|
+
Math.abs(daysBetween(d.to.value.date, day))
|
|
1030
1024
|
? 'from'
|
|
1031
1025
|
: 'to';
|
|
1032
1026
|
const end = moveNearest ? nearest : 'from';
|
|
1033
1027
|
setTypingBlock('from', false);
|
|
1034
1028
|
setTypingBlock('to', false);
|
|
1035
|
-
draft.selectDay(day, fixedTime ??
|
|
1029
|
+
draft.selectDay(day, fixedTime ?? pending[end] ?? newTime());
|
|
1036
1030
|
setActiveEnd(moveNearest ? nearest : 'to');
|
|
1037
1031
|
return;
|
|
1038
1032
|
}
|
|
1039
1033
|
// Choosing the end after a start tap: a day before the start moves the start.
|
|
1040
1034
|
const end1: RangeEnd =
|
|
1041
|
-
|
|
1035
|
+
activeEnd === 'to' &&
|
|
1042
1036
|
!endPicked &&
|
|
1043
|
-
|
|
1044
|
-
compareCalendarDates(day,
|
|
1037
|
+
d.from.value &&
|
|
1038
|
+
compareCalendarDates(day, d.from.value.date) < 0
|
|
1045
1039
|
? 'from'
|
|
1046
|
-
:
|
|
1040
|
+
: activeEnd;
|
|
1047
1041
|
// The tapped day replaces whatever was typed in this end only.
|
|
1048
1042
|
setTypingBlock(end1, false);
|
|
1049
1043
|
draft.setRangeEnd(
|
|
1050
1044
|
day,
|
|
1051
1045
|
end1,
|
|
1052
1046
|
end1 === 'to'
|
|
1053
|
-
? endTimeFor(
|
|
1054
|
-
: (fixedTime ??
|
|
1047
|
+
? endTimeFor(d.from.value, day, pending.to)
|
|
1048
|
+
: (fixedTime ?? pending.from ?? newTime())
|
|
1055
1049
|
);
|
|
1056
1050
|
// The start tapped again where a range can't be one day deselects
|
|
1057
1051
|
// it, so the next tap picks the start again.
|
|
1058
1052
|
const deselects =
|
|
1059
1053
|
end1 === 'to' &&
|
|
1060
1054
|
!sameDayRangeOk(draftOptions) &&
|
|
1061
|
-
isSameDay(
|
|
1062
|
-
if (
|
|
1055
|
+
isSameDay(d.from.value?.date, day);
|
|
1056
|
+
if (activeEnd === 'from') setActiveEnd('to');
|
|
1063
1057
|
else if (deselects) setActiveEnd('from');
|
|
1064
1058
|
return;
|
|
1065
1059
|
}
|
|
1066
1060
|
// The single field: the first tap is the start, the next one the end.
|
|
1067
1061
|
const startsOver =
|
|
1068
|
-
!
|
|
1069
|
-
(!!
|
|
1062
|
+
!d.from.value ||
|
|
1063
|
+
(!!d.to.value && range?.reselect !== 'moveNearestEnd');
|
|
1070
1064
|
const end: RangeEnd = startsOver ? 'from' : 'to';
|
|
1071
1065
|
setTypingBlock('single', false);
|
|
1072
1066
|
draft.selectDay(
|
|
1073
1067
|
day,
|
|
1074
|
-
end === 'to' && !
|
|
1075
|
-
? endTimeFor(
|
|
1076
|
-
: (fixedTime ??
|
|
1068
|
+
end === 'to' && !d.to.value
|
|
1069
|
+
? endTimeFor(d.from.value, day, pending.to)
|
|
1070
|
+
: (fixedTime ?? pending[end] ?? newTime())
|
|
1077
1071
|
);
|
|
1078
1072
|
return;
|
|
1079
1073
|
}
|
|
1080
|
-
if (
|
|
1074
|
+
if (d.mode === 'multiple') {
|
|
1081
1075
|
const perDate = type === 'datetime' && multipleTimeMode === 'perDate';
|
|
1082
|
-
const picked =
|
|
1083
|
-
isSameDay(slot.value?.date, day)
|
|
1084
|
-
);
|
|
1076
|
+
const picked = d.slots.some((slot) => isSameDay(slot.value?.date, day));
|
|
1085
1077
|
// perDate: a tap on a picked day first makes it the one the time row edits.
|
|
1086
|
-
if (perDate && picked && !isSameDay(
|
|
1078
|
+
if (perDate && picked && !isSameDay(activeDay, day)) {
|
|
1087
1079
|
setActiveDay(day);
|
|
1088
1080
|
return;
|
|
1089
1081
|
}
|
|
1090
|
-
draft.selectDay(day, fixedTime ??
|
|
1082
|
+
draft.selectDay(day, fixedTime ?? newTime());
|
|
1091
1083
|
if (perDate) setActiveDay(picked ? null : day);
|
|
1092
1084
|
return;
|
|
1093
1085
|
}
|
|
1094
1086
|
setTypingBlock('single', false);
|
|
1095
|
-
draft.selectDay(day, fixedTime ??
|
|
1087
|
+
draft.selectDay(day, fixedTime ?? pending.single ?? newTime());
|
|
1096
1088
|
},
|
|
1097
1089
|
// eslint-disable-next-line react-hooks/exhaustive-deps -- valuesOf reads only `d` and props listed here
|
|
1098
1090
|
[
|
|
1099
1091
|
d,
|
|
1100
1092
|
draft,
|
|
1101
|
-
|
|
1093
|
+
newTime,
|
|
1102
1094
|
rangeLayout,
|
|
1103
1095
|
activeEnd,
|
|
1104
1096
|
activeDay,
|
|
@@ -1108,13 +1100,11 @@ const DateTimePicker: React.FC<DateTimePickerProps> = (props) => {
|
|
|
1108
1100
|
type,
|
|
1109
1101
|
multipleTimeMode,
|
|
1110
1102
|
setTypingBlock,
|
|
1111
|
-
draftSystem,
|
|
1112
|
-
shownSystem,
|
|
1113
|
-
resetSelectionUi,
|
|
1114
1103
|
endPicked,
|
|
1115
1104
|
draftOptions,
|
|
1116
1105
|
minuteStep,
|
|
1117
1106
|
constraints,
|
|
1107
|
+
revealTime,
|
|
1118
1108
|
]
|
|
1119
1109
|
);
|
|
1120
1110
|
|
|
@@ -1271,12 +1261,13 @@ const DateTimePicker: React.FC<DateTimePickerProps> = (props) => {
|
|
|
1271
1261
|
? (v) =>
|
|
1272
1262
|
hijriDateText(v.date, hijriLocale) ?? formatDateText(v, dateFormat)
|
|
1273
1263
|
: undefined;
|
|
1274
|
-
|
|
1275
|
-
const
|
|
1276
|
-
const
|
|
1277
|
-
const
|
|
1278
|
-
|
|
1279
|
-
//
|
|
1264
|
+
// The calendar the on-screen field writes the saved value in.
|
|
1265
|
+
const fieldSystem = fieldCalendar === 'gregory' ? 'gregory' : savedSystem;
|
|
1266
|
+
const savedDateText = dateTextFor(fieldSystem);
|
|
1267
|
+
const draftDateText = dateTextFor(shownSystem);
|
|
1268
|
+
const savedStrings = stringsFor(fieldSystem);
|
|
1269
|
+
// The texts inside the picker, its error messages too, follow the tab on
|
|
1270
|
+
// screen; the on-screen field's keep `strings`.
|
|
1280
1271
|
const shownStrings = stringsFor(shownSystem);
|
|
1281
1272
|
|
|
1282
1273
|
// What the on-screen field shows: the saved value, never the draft.
|
|
@@ -1353,7 +1344,7 @@ const DateTimePicker: React.FC<DateTimePickerProps> = (props) => {
|
|
|
1353
1344
|
type,
|
|
1354
1345
|
mode,
|
|
1355
1346
|
value,
|
|
1356
|
-
displayText: digitsFor(
|
|
1347
|
+
displayText: digitsFor(fieldSystem)(
|
|
1357
1348
|
savedText ??
|
|
1358
1349
|
formatValueText(
|
|
1359
1350
|
savedSingle,
|
|
@@ -1388,8 +1379,12 @@ const DateTimePicker: React.FC<DateTimePickerProps> = (props) => {
|
|
|
1388
1379
|
const sheetValue =
|
|
1389
1380
|
draft.draft.mode === 'single' ? draft.draft.slot.value : null;
|
|
1390
1381
|
// A time outside the limits is explained under the time row, not here.
|
|
1382
|
+
const notAllowedError =
|
|
1383
|
+
sheetOptions?.showNotAllowedError === false
|
|
1384
|
+
? ''
|
|
1385
|
+
: shownStrings.errorNotAllowed;
|
|
1391
1386
|
const sheetValueError =
|
|
1392
|
-
sheetValue && dayOff(sheetValue.date) ?
|
|
1387
|
+
sheetValue && dayOff(sheetValue.date) ? notAllowedError : '';
|
|
1393
1388
|
|
|
1394
1389
|
let rangeError = '';
|
|
1395
1390
|
if (d.mode === 'range' && d.from.value && d.to.value) {
|
|
@@ -1398,7 +1393,7 @@ const DateTimePicker: React.FC<DateTimePickerProps> = (props) => {
|
|
|
1398
1393
|
constraints.dayStatus(d.from.value.date).disabled ||
|
|
1399
1394
|
constraints.dayStatus(d.to.value.date).disabled
|
|
1400
1395
|
)
|
|
1401
|
-
rangeError =
|
|
1396
|
+
rangeError = notAllowedError;
|
|
1402
1397
|
else if (previewIssue === 'RANGE_ORDER')
|
|
1403
1398
|
rangeError = shownStrings.errorRangeOrder;
|
|
1404
1399
|
else if (previewIssue === 'RANGE_LENGTH')
|
|
@@ -1473,7 +1468,7 @@ const DateTimePicker: React.FC<DateTimePickerProps> = (props) => {
|
|
|
1473
1468
|
) : mode === 'single' ? (
|
|
1474
1469
|
<ValueField
|
|
1475
1470
|
{...fieldCommon}
|
|
1476
|
-
strings={
|
|
1471
|
+
strings={shownStrings}
|
|
1477
1472
|
errorStrings={shownStrings}
|
|
1478
1473
|
value={sheetValue}
|
|
1479
1474
|
displayText={precision !== 'day' ? periodText(sheetValue) : undefined}
|
|
@@ -1492,7 +1487,7 @@ const DateTimePicker: React.FC<DateTimePickerProps> = (props) => {
|
|
|
1492
1487
|
) : d.mode === 'range' ? (
|
|
1493
1488
|
<RangeFields
|
|
1494
1489
|
{...fieldCommon}
|
|
1495
|
-
strings={
|
|
1490
|
+
strings={shownStrings}
|
|
1496
1491
|
errorStrings={shownStrings}
|
|
1497
1492
|
layout={rangeLayout}
|
|
1498
1493
|
dateText={draftDateText}
|
|
@@ -1514,7 +1509,7 @@ const DateTimePicker: React.FC<DateTimePickerProps> = (props) => {
|
|
|
1514
1509
|
) : d.mode === 'multiple' ? (
|
|
1515
1510
|
<ValueField
|
|
1516
1511
|
{...fieldCommon}
|
|
1517
|
-
strings={
|
|
1512
|
+
strings={shownStrings}
|
|
1518
1513
|
value={null}
|
|
1519
1514
|
displayText={formatMultipleText(
|
|
1520
1515
|
draftValues,
|
|
@@ -1522,7 +1517,7 @@ const DateTimePicker: React.FC<DateTimePickerProps> = (props) => {
|
|
|
1522
1517
|
dateFormat,
|
|
1523
1518
|
timeFormat,
|
|
1524
1519
|
draftDateText,
|
|
1525
|
-
|
|
1520
|
+
shownStrings
|
|
1526
1521
|
)}
|
|
1527
1522
|
placeholder={sheetOptions?.fieldPlaceholder}
|
|
1528
1523
|
allowClear={allowClear}
|
|
@@ -1541,13 +1536,20 @@ const DateTimePicker: React.FC<DateTimePickerProps> = (props) => {
|
|
|
1541
1536
|
};
|
|
1542
1537
|
const systemA11yLabel =
|
|
1543
1538
|
strings.a11yCalendarSystem ?? DEFAULT_STRINGS.a11yCalendarSystem;
|
|
1539
|
+
// A calendar or tab the user picks is an edit: 'instant' saves the selection
|
|
1540
|
+
// again with that calendar.
|
|
1544
1541
|
const chooseSystem = (next: CalendarSystem) => {
|
|
1545
1542
|
if (next === system) return;
|
|
1543
|
+
edited.current = true;
|
|
1546
1544
|
setChosenSystem(next);
|
|
1547
1545
|
if (next === 'dual') setDualTab(dualDefaultTab);
|
|
1548
1546
|
echo.current = next;
|
|
1549
1547
|
onCalendarSystemChange?.(next);
|
|
1550
1548
|
};
|
|
1549
|
+
const chooseTab = (next: 'gregory' | 'hijri') => {
|
|
1550
|
+
edited.current = true;
|
|
1551
|
+
setDualTab(next);
|
|
1552
|
+
};
|
|
1551
1553
|
const calendarSwitchNode = !switchSystems ? null : renderCalendarSwitch ? (
|
|
1552
1554
|
renderCalendarSwitch({
|
|
1553
1555
|
systems: switchSystems,
|
|
@@ -1579,7 +1581,7 @@ const DateTimePicker: React.FC<DateTimePickerProps> = (props) => {
|
|
|
1579
1581
|
{ value: 'hijri', label: systemLabels.hijri },
|
|
1580
1582
|
]}
|
|
1581
1583
|
selected={dualTab}
|
|
1582
|
-
onSelect={
|
|
1584
|
+
onSelect={chooseTab}
|
|
1583
1585
|
accessibilityLabel={systemA11yLabel}
|
|
1584
1586
|
tokens={tokens}
|
|
1585
1587
|
styles={styles}
|
|
@@ -1595,12 +1597,9 @@ const DateTimePicker: React.FC<DateTimePickerProps> = (props) => {
|
|
|
1595
1597
|
<View style={hijriShown ? RTL : undefined}>{node}</View>
|
|
1596
1598
|
</RtlContext.Provider>
|
|
1597
1599
|
);
|
|
1598
|
-
// Another calendar opens on the days the last one showed, else
|
|
1599
|
-
//
|
|
1600
|
-
const calendarAnchor =
|
|
1601
|
-
shownAnchor.current ??
|
|
1602
|
-
(selectionShown ? null : firstSelectedDay(selection)) ??
|
|
1603
|
-
initialVisibleDate;
|
|
1600
|
+
// Another calendar opens on the days the last one showed, else where the
|
|
1601
|
+
// picker would open.
|
|
1602
|
+
const calendarAnchor = shownAnchor.current ?? initialVisibleDate;
|
|
1604
1603
|
|
|
1605
1604
|
const calendar = (
|
|
1606
1605
|
<View style={folded ? FOLDED : undefined}>
|
|
@@ -1609,10 +1608,10 @@ const DateTimePicker: React.FC<DateTimePickerProps> = (props) => {
|
|
|
1609
1608
|
{mirrored(
|
|
1610
1609
|
<Calendar
|
|
1611
1610
|
key={shownSystem}
|
|
1612
|
-
constraints={
|
|
1611
|
+
constraints={calendarConstraints}
|
|
1613
1612
|
today={today}
|
|
1614
1613
|
initialVisibleDate={calendarAnchor}
|
|
1615
|
-
selection={
|
|
1614
|
+
selection={selection}
|
|
1616
1615
|
onSelectDay={onSelectDayStable}
|
|
1617
1616
|
firstDayOfWeek={firstDayOfWeek}
|
|
1618
1617
|
showOutsideDays={showOutsideDays}
|
|
@@ -1688,19 +1687,25 @@ const DateTimePicker: React.FC<DateTimePickerProps> = (props) => {
|
|
|
1688
1687
|
? validation.message
|
|
1689
1688
|
: null;
|
|
1690
1689
|
|
|
1691
|
-
// The field
|
|
1692
|
-
// the calendar and the time rows follow the tab that shows.
|
|
1690
|
+
// The field, the calendar and the time rows follow the tab that shows.
|
|
1693
1691
|
const sheetContent = (
|
|
1694
1692
|
<>
|
|
1695
|
-
<NumeralsContext.Provider value={digitsFor(draftSystem)}>
|
|
1696
|
-
{sheetField}
|
|
1697
|
-
</NumeralsContext.Provider>
|
|
1698
1693
|
<NumeralsContext.Provider value={digitsFor(shownSystem)}>
|
|
1694
|
+
{sheetField}
|
|
1699
1695
|
{calendar}
|
|
1700
1696
|
</NumeralsContext.Provider>
|
|
1701
|
-
|
|
1702
|
-
|
|
1703
|
-
|
|
1697
|
+
{timeRow == null ? null : (
|
|
1698
|
+
// Measured for the scroll to the time (sheet.scrollToTime).
|
|
1699
|
+
<View
|
|
1700
|
+
ref={timeRef}
|
|
1701
|
+
collapsable={false}
|
|
1702
|
+
testID={testID ? `${testID}-time-section` : undefined}
|
|
1703
|
+
>
|
|
1704
|
+
<NumeralsContext.Provider value={digitsFor(shownSystem)}>
|
|
1705
|
+
{mirrored(timeRow)}
|
|
1706
|
+
</NumeralsContext.Provider>
|
|
1707
|
+
</View>
|
|
1708
|
+
)}
|
|
1704
1709
|
{validationMessage ? (
|
|
1705
1710
|
<FieldMessages
|
|
1706
1711
|
error={validationMessage}
|
|
@@ -1716,7 +1721,7 @@ const DateTimePicker: React.FC<DateTimePickerProps> = (props) => {
|
|
|
1716
1721
|
);
|
|
1717
1722
|
|
|
1718
1723
|
return (
|
|
1719
|
-
<NumeralsContext.Provider value={digitsFor(
|
|
1724
|
+
<NumeralsContext.Provider value={digitsFor(fieldSystem)}>
|
|
1720
1725
|
{trigger}
|
|
1721
1726
|
<PickerContainer
|
|
1722
1727
|
presentation={presentation}
|
|
@@ -1724,6 +1729,7 @@ const DateTimePicker: React.FC<DateTimePickerProps> = (props) => {
|
|
|
1724
1729
|
onRequestClose={close}
|
|
1725
1730
|
renderSheet={renderSheet}
|
|
1726
1731
|
sheet={sheetOptions}
|
|
1732
|
+
scrollRef={scrollRef}
|
|
1727
1733
|
state={state}
|
|
1728
1734
|
header={header}
|
|
1729
1735
|
title={title ?? (isInline ? undefined : strings.titleDate)}
|