rn-grid-date-time-picker 1.3.0 → 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 +21 -0
- package/lib/commonjs/components/DateTimePicker.js +67 -115
- 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/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/theme/styleKeys.js +3 -1
- package/lib/commonjs/components/theme/styleKeys.js.map +1 -1
- package/lib/commonjs/components/types.js +2 -1
- package/lib/commonjs/components/types.js.map +1 -1
- package/lib/module/components/DateTimePicker.js +68 -116
- 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/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/theme/styleKeys.js +3 -1
- package/lib/module/components/theme/styleKeys.js.map +1 -1
- package/lib/module/components/types.js +2 -1
- package/lib/module/components/types.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/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/theme/styleKeys.d.ts.map +1 -1
- package/lib/typescript/commonjs/src/components/types.d.ts +18 -5
- package/lib/typescript/commonjs/src/components/types.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/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/theme/styleKeys.d.ts.map +1 -1
- package/lib/typescript/module/src/components/types.d.ts +18 -5
- package/lib/typescript/module/src/components/types.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/components/DateTimePicker.tsx +79 -120
- package/src/components/calendar/DayCell.tsx +25 -6
- package/src/components/calendar/calendarStyles.ts +11 -0
- package/src/components/field/ValueField.tsx +30 -11
- package/src/components/field/fieldText.ts +48 -5
- package/src/components/theme/styleKeys.ts +3 -1
- package/src/components/types.ts +18 -5
|
@@ -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,
|
|
@@ -95,14 +94,6 @@ const sameValue = (a: DateTimeValue, b: DateTimeValue): boolean =>
|
|
|
95
94
|
const ALL_SYSTEMS: CalendarSystem[] = ['gregory', 'hijri', 'dual'];
|
|
96
95
|
const RTL = { direction: 'rtl' } as const;
|
|
97
96
|
|
|
98
|
-
/** The same kind of selection with nothing in it. */
|
|
99
|
-
const emptySelection = (sel: CalendarSelection): CalendarSelection =>
|
|
100
|
-
sel.kind === 'single'
|
|
101
|
-
? { kind: 'single', day: null }
|
|
102
|
-
: sel.kind === 'range'
|
|
103
|
-
? { kind: 'range', from: null, to: null }
|
|
104
|
-
: { ...sel, days: [] };
|
|
105
|
-
|
|
106
97
|
/** Minute steps that divide the hour. */
|
|
107
98
|
const MINUTE_STEPS = [1, 2, 3, 4, 5, 6, 10, 12, 15, 20, 30, 60];
|
|
108
99
|
|
|
@@ -741,10 +732,10 @@ const DateTimePicker: React.FC<DateTimePickerProps> = (props) => {
|
|
|
741
732
|
latest.current = { validate, onError, value };
|
|
742
733
|
const valueKey = JSON.stringify(value ?? '');
|
|
743
734
|
|
|
744
|
-
// A selection
|
|
745
|
-
//
|
|
746
|
-
// value remembers its calendar; one from outside counts as picked in
|
|
747
|
-
// 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.
|
|
748
739
|
// The saved value as a draft, for the on-screen field and for comparing
|
|
749
740
|
// values by what they hold.
|
|
750
741
|
const saved = useMemo(
|
|
@@ -763,27 +754,14 @@ const DateTimePicker: React.FC<DateTimePickerProps> = (props) => {
|
|
|
763
754
|
savedSystemRef.current?.content === savedContent
|
|
764
755
|
? savedSystemRef.current.system
|
|
765
756
|
: outsideSystem;
|
|
766
|
-
const
|
|
767
|
-
//
|
|
768
|
-
//
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
setDraftSystem(savedSystem);
|
|
775
|
-
const systemOnly =
|
|
776
|
-
seen.isOpen === isOpen &&
|
|
777
|
-
seen.valueKey === valueKey &&
|
|
778
|
-
seen.savedSystem !== savedSystem;
|
|
779
|
-
if (systemOnly && isOpen && edited.current) {
|
|
780
|
-
draft.clear();
|
|
781
|
-
resetSelectionUi();
|
|
782
|
-
}
|
|
783
|
-
// eslint-disable-next-line react-hooks/exhaustive-deps -- the draft's actions are stable
|
|
784
|
-
}, [isOpen, valueKey, savedSystem]);
|
|
785
|
-
useEffect(() => setDualTab(dualDefaultTab), [isOpen, dualDefaultTab]);
|
|
786
|
-
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
|
+
);
|
|
787
765
|
/** Saves a value: with the calendar it was picked in, when one can be Hijri. */
|
|
788
766
|
const emitChange = useCallback(
|
|
789
767
|
(wire: PickerValue, picked: 'gregory' | 'hijri') => {
|
|
@@ -821,13 +799,14 @@ const DateTimePicker: React.FC<DateTimePickerProps> = (props) => {
|
|
|
821
799
|
!wheelSpinning &&
|
|
822
800
|
!confirmDisabled;
|
|
823
801
|
|
|
824
|
-
// 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.
|
|
825
804
|
const commit = useCallback(
|
|
826
805
|
(wire: PickerValue) => {
|
|
827
|
-
emitChange(wire,
|
|
806
|
+
emitChange(wire, shownSystem);
|
|
828
807
|
onBlur?.();
|
|
829
808
|
},
|
|
830
|
-
[emitChange,
|
|
809
|
+
[emitChange, shownSystem, onBlur]
|
|
831
810
|
);
|
|
832
811
|
|
|
833
812
|
const confirm = useCallback(() => {
|
|
@@ -865,8 +844,8 @@ const DateTimePicker: React.FC<DateTimePickerProps> = (props) => {
|
|
|
865
844
|
draft.clear();
|
|
866
845
|
resetSelectionUi();
|
|
867
846
|
edited.current = false;
|
|
868
|
-
emitChange('',
|
|
869
|
-
}, [draft, emitChange,
|
|
847
|
+
emitChange('', shownSystem);
|
|
848
|
+
}, [draft, emitChange, shownSystem, resetSelectionUi]);
|
|
870
849
|
|
|
871
850
|
const clearDraft = useCallback(() => {
|
|
872
851
|
edited.current = true;
|
|
@@ -876,7 +855,6 @@ const DateTimePicker: React.FC<DateTimePickerProps> = (props) => {
|
|
|
876
855
|
|
|
877
856
|
// × in the field above the calendar. 'instant' saves every edit, so it
|
|
878
857
|
// clears the saved value too (once; an empty value isn't saved again).
|
|
879
|
-
const hasSaved = valuesOf(saved).some((v) => v !== null);
|
|
880
858
|
const clearInSheet = useCallback(() => {
|
|
881
859
|
if (commitMode === 'instant' && hasSaved) clearValue();
|
|
882
860
|
else clearDraft();
|
|
@@ -889,7 +867,8 @@ const DateTimePicker: React.FC<DateTimePickerProps> = (props) => {
|
|
|
889
867
|
// Confirm press, but only while open (closing discards, as with Confirm),
|
|
890
868
|
// and only what the draft holds: a time still being typed isn't saved yet.
|
|
891
869
|
// An edit equal to the saved value isn't saved again; one `validate`
|
|
892
|
-
// 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.
|
|
893
872
|
const lastRejected = useRef<string | null>(null);
|
|
894
873
|
// The last value saved: not saved again while the parent's `value` catches
|
|
895
874
|
// up (a slow store, a server round trip) or when it never does.
|
|
@@ -911,12 +890,13 @@ const DateTimePicker: React.FC<DateTimePickerProps> = (props) => {
|
|
|
911
890
|
if (!typingDone || timeIssue || !canSave(draft.draft)) return;
|
|
912
891
|
const wire = draft.toWire();
|
|
913
892
|
const key = JSON.stringify(wire);
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
// 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.
|
|
917
897
|
const unchanged =
|
|
918
898
|
key === valueKey || contentOf(draft.draft) === savedContent;
|
|
919
|
-
if (unchanged &&
|
|
899
|
+
if (unchanged && shownSystem === savedSystem) return;
|
|
920
900
|
const message = latest.current.validate?.(wire);
|
|
921
901
|
if (message) {
|
|
922
902
|
lastRejected.current = key;
|
|
@@ -924,7 +904,7 @@ const DateTimePicker: React.FC<DateTimePickerProps> = (props) => {
|
|
|
924
904
|
latest.current.onError?.('VALIDATION', wire);
|
|
925
905
|
return;
|
|
926
906
|
}
|
|
927
|
-
lastEmitted.current =
|
|
907
|
+
lastEmitted.current = sent;
|
|
928
908
|
commit(wire);
|
|
929
909
|
};
|
|
930
910
|
// Only typing waits for the field to be left; a tap is a finished edit.
|
|
@@ -937,7 +917,7 @@ const DateTimePicker: React.FC<DateTimePickerProps> = (props) => {
|
|
|
937
917
|
isOpen,
|
|
938
918
|
sheetTyping,
|
|
939
919
|
draft.draft,
|
|
940
|
-
|
|
920
|
+
shownSystem,
|
|
941
921
|
timeIssue,
|
|
942
922
|
typingBlocked,
|
|
943
923
|
wheelSpinning,
|
|
@@ -968,16 +948,9 @@ const DateTimePicker: React.FC<DateTimePickerProps> = (props) => {
|
|
|
968
948
|
}
|
|
969
949
|
: null);
|
|
970
950
|
|
|
971
|
-
// Typed segments are Gregorian, so typing keeps the calendar a selection was
|
|
972
|
-
// picked in; into an empty draft, it takes the calendar on screen.
|
|
973
|
-
const adoptShownIfEmpty = () => {
|
|
974
|
-
if (valuesOf(d).every((v) => v === null)) setDraftSystem(shownSystem);
|
|
975
|
-
};
|
|
976
|
-
|
|
977
951
|
const onSheetTyped = (parsed: ParsedSegments) => {
|
|
978
952
|
edited.current = true;
|
|
979
953
|
typedLast.current = true;
|
|
980
|
-
adoptShownIfEmpty();
|
|
981
954
|
const typed = withFilledTime(parsed, 'single');
|
|
982
955
|
setTypingBlock('single', !parsed.isEmpty && !typed);
|
|
983
956
|
if (typed) draft.setValue(typed, 'single');
|
|
@@ -987,7 +960,6 @@ const DateTimePicker: React.FC<DateTimePickerProps> = (props) => {
|
|
|
987
960
|
const onRangeTyped = (end: RangeEnd, parsed: ParsedSegments) => {
|
|
988
961
|
edited.current = true;
|
|
989
962
|
typedLast.current = true;
|
|
990
|
-
adoptShownIfEmpty();
|
|
991
963
|
const typed = withFilledTime(parsed, end);
|
|
992
964
|
setTypingBlock(end, !parsed.isEmpty && !typed);
|
|
993
965
|
if (typed || parsed.isEmpty) draft.setValue(typed, end);
|
|
@@ -1038,86 +1010,72 @@ const DateTimePicker: React.FC<DateTimePickerProps> = (props) => {
|
|
|
1038
1010
|
(day: CalendarDate) => {
|
|
1039
1011
|
edited.current = true;
|
|
1040
1012
|
typedLast.current = false;
|
|
1041
|
-
// A pick in the other calendar starts over: the selection is one calendar's.
|
|
1042
|
-
const fresh =
|
|
1043
|
-
draftSystem !== shownSystem && valuesOf(d).some((v) => v !== null);
|
|
1044
|
-
setDraftSystem(shownSystem);
|
|
1045
|
-
const cur = fresh ? emptied(d) : d;
|
|
1046
|
-
const end0: RangeEnd = fresh ? 'from' : activeEnd;
|
|
1047
|
-
const dayInEdit = fresh ? null : activeDay;
|
|
1048
|
-
const times: PendingTimes = fresh ? {} : pending;
|
|
1049
|
-
if (fresh) {
|
|
1050
|
-
draft.clear();
|
|
1051
|
-
resetSelectionUi();
|
|
1052
|
-
}
|
|
1053
1013
|
// A multiple picker scrolls for its first date only, so more can be picked.
|
|
1054
|
-
if (
|
|
1055
|
-
if (
|
|
1014
|
+
if (d.mode !== 'multiple' || d.slots.length === 0) revealTime();
|
|
1015
|
+
if (d.mode === 'range') {
|
|
1056
1016
|
if (rangeLayout === 'fromTo') {
|
|
1057
1017
|
setEndPicked(false);
|
|
1058
1018
|
// A full range: the tap starts a new one (or moves the nearest end).
|
|
1059
|
-
if (!endPicked &&
|
|
1019
|
+
if (!endPicked && d.from.value && d.to.value) {
|
|
1060
1020
|
const moveNearest = range?.reselect === 'moveNearestEnd';
|
|
1061
1021
|
const nearest: RangeEnd =
|
|
1062
|
-
Math.abs(daysBetween(
|
|
1063
|
-
Math.abs(daysBetween(
|
|
1022
|
+
Math.abs(daysBetween(d.from.value.date, day)) <
|
|
1023
|
+
Math.abs(daysBetween(d.to.value.date, day))
|
|
1064
1024
|
? 'from'
|
|
1065
1025
|
: 'to';
|
|
1066
1026
|
const end = moveNearest ? nearest : 'from';
|
|
1067
1027
|
setTypingBlock('from', false);
|
|
1068
1028
|
setTypingBlock('to', false);
|
|
1069
|
-
draft.selectDay(day, fixedTime ??
|
|
1029
|
+
draft.selectDay(day, fixedTime ?? pending[end] ?? newTime());
|
|
1070
1030
|
setActiveEnd(moveNearest ? nearest : 'to');
|
|
1071
1031
|
return;
|
|
1072
1032
|
}
|
|
1073
1033
|
// Choosing the end after a start tap: a day before the start moves the start.
|
|
1074
1034
|
const end1: RangeEnd =
|
|
1075
|
-
|
|
1035
|
+
activeEnd === 'to' &&
|
|
1076
1036
|
!endPicked &&
|
|
1077
|
-
|
|
1078
|
-
compareCalendarDates(day,
|
|
1037
|
+
d.from.value &&
|
|
1038
|
+
compareCalendarDates(day, d.from.value.date) < 0
|
|
1079
1039
|
? 'from'
|
|
1080
|
-
:
|
|
1040
|
+
: activeEnd;
|
|
1081
1041
|
// The tapped day replaces whatever was typed in this end only.
|
|
1082
1042
|
setTypingBlock(end1, false);
|
|
1083
1043
|
draft.setRangeEnd(
|
|
1084
1044
|
day,
|
|
1085
1045
|
end1,
|
|
1086
1046
|
end1 === 'to'
|
|
1087
|
-
? endTimeFor(
|
|
1088
|
-
: (fixedTime ??
|
|
1047
|
+
? endTimeFor(d.from.value, day, pending.to)
|
|
1048
|
+
: (fixedTime ?? pending.from ?? newTime())
|
|
1089
1049
|
);
|
|
1090
1050
|
// The start tapped again where a range can't be one day deselects
|
|
1091
1051
|
// it, so the next tap picks the start again.
|
|
1092
1052
|
const deselects =
|
|
1093
1053
|
end1 === 'to' &&
|
|
1094
1054
|
!sameDayRangeOk(draftOptions) &&
|
|
1095
|
-
isSameDay(
|
|
1096
|
-
if (
|
|
1055
|
+
isSameDay(d.from.value?.date, day);
|
|
1056
|
+
if (activeEnd === 'from') setActiveEnd('to');
|
|
1097
1057
|
else if (deselects) setActiveEnd('from');
|
|
1098
1058
|
return;
|
|
1099
1059
|
}
|
|
1100
1060
|
// The single field: the first tap is the start, the next one the end.
|
|
1101
1061
|
const startsOver =
|
|
1102
|
-
!
|
|
1103
|
-
(!!
|
|
1062
|
+
!d.from.value ||
|
|
1063
|
+
(!!d.to.value && range?.reselect !== 'moveNearestEnd');
|
|
1104
1064
|
const end: RangeEnd = startsOver ? 'from' : 'to';
|
|
1105
1065
|
setTypingBlock('single', false);
|
|
1106
1066
|
draft.selectDay(
|
|
1107
1067
|
day,
|
|
1108
|
-
end === 'to' && !
|
|
1109
|
-
? endTimeFor(
|
|
1110
|
-
: (fixedTime ??
|
|
1068
|
+
end === 'to' && !d.to.value
|
|
1069
|
+
? endTimeFor(d.from.value, day, pending.to)
|
|
1070
|
+
: (fixedTime ?? pending[end] ?? newTime())
|
|
1111
1071
|
);
|
|
1112
1072
|
return;
|
|
1113
1073
|
}
|
|
1114
|
-
if (
|
|
1074
|
+
if (d.mode === 'multiple') {
|
|
1115
1075
|
const perDate = type === 'datetime' && multipleTimeMode === 'perDate';
|
|
1116
|
-
const picked =
|
|
1117
|
-
isSameDay(slot.value?.date, day)
|
|
1118
|
-
);
|
|
1076
|
+
const picked = d.slots.some((slot) => isSameDay(slot.value?.date, day));
|
|
1119
1077
|
// perDate: a tap on a picked day first makes it the one the time row edits.
|
|
1120
|
-
if (perDate && picked && !isSameDay(
|
|
1078
|
+
if (perDate && picked && !isSameDay(activeDay, day)) {
|
|
1121
1079
|
setActiveDay(day);
|
|
1122
1080
|
return;
|
|
1123
1081
|
}
|
|
@@ -1126,7 +1084,7 @@ const DateTimePicker: React.FC<DateTimePickerProps> = (props) => {
|
|
|
1126
1084
|
return;
|
|
1127
1085
|
}
|
|
1128
1086
|
setTypingBlock('single', false);
|
|
1129
|
-
draft.selectDay(day, fixedTime ??
|
|
1087
|
+
draft.selectDay(day, fixedTime ?? pending.single ?? newTime());
|
|
1130
1088
|
},
|
|
1131
1089
|
// eslint-disable-next-line react-hooks/exhaustive-deps -- valuesOf reads only `d` and props listed here
|
|
1132
1090
|
[
|
|
@@ -1142,9 +1100,6 @@ const DateTimePicker: React.FC<DateTimePickerProps> = (props) => {
|
|
|
1142
1100
|
type,
|
|
1143
1101
|
multipleTimeMode,
|
|
1144
1102
|
setTypingBlock,
|
|
1145
|
-
draftSystem,
|
|
1146
|
-
shownSystem,
|
|
1147
|
-
resetSelectionUi,
|
|
1148
1103
|
endPicked,
|
|
1149
1104
|
draftOptions,
|
|
1150
1105
|
minuteStep,
|
|
@@ -1309,11 +1264,10 @@ const DateTimePicker: React.FC<DateTimePickerProps> = (props) => {
|
|
|
1309
1264
|
// The calendar the on-screen field writes the saved value in.
|
|
1310
1265
|
const fieldSystem = fieldCalendar === 'gregory' ? 'gregory' : savedSystem;
|
|
1311
1266
|
const savedDateText = dateTextFor(fieldSystem);
|
|
1312
|
-
const draftDateText = dateTextFor(
|
|
1267
|
+
const draftDateText = dateTextFor(shownSystem);
|
|
1313
1268
|
const savedStrings = stringsFor(fieldSystem);
|
|
1314
|
-
|
|
1315
|
-
//
|
|
1316
|
-
// field's keep `strings`.
|
|
1269
|
+
// The texts inside the picker, its error messages too, follow the tab on
|
|
1270
|
+
// screen; the on-screen field's keep `strings`.
|
|
1317
1271
|
const shownStrings = stringsFor(shownSystem);
|
|
1318
1272
|
|
|
1319
1273
|
// What the on-screen field shows: the saved value, never the draft.
|
|
@@ -1425,8 +1379,12 @@ const DateTimePicker: React.FC<DateTimePickerProps> = (props) => {
|
|
|
1425
1379
|
const sheetValue =
|
|
1426
1380
|
draft.draft.mode === 'single' ? draft.draft.slot.value : null;
|
|
1427
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;
|
|
1428
1386
|
const sheetValueError =
|
|
1429
|
-
sheetValue && dayOff(sheetValue.date) ?
|
|
1387
|
+
sheetValue && dayOff(sheetValue.date) ? notAllowedError : '';
|
|
1430
1388
|
|
|
1431
1389
|
let rangeError = '';
|
|
1432
1390
|
if (d.mode === 'range' && d.from.value && d.to.value) {
|
|
@@ -1435,7 +1393,7 @@ const DateTimePicker: React.FC<DateTimePickerProps> = (props) => {
|
|
|
1435
1393
|
constraints.dayStatus(d.from.value.date).disabled ||
|
|
1436
1394
|
constraints.dayStatus(d.to.value.date).disabled
|
|
1437
1395
|
)
|
|
1438
|
-
rangeError =
|
|
1396
|
+
rangeError = notAllowedError;
|
|
1439
1397
|
else if (previewIssue === 'RANGE_ORDER')
|
|
1440
1398
|
rangeError = shownStrings.errorRangeOrder;
|
|
1441
1399
|
else if (previewIssue === 'RANGE_LENGTH')
|
|
@@ -1510,7 +1468,7 @@ const DateTimePicker: React.FC<DateTimePickerProps> = (props) => {
|
|
|
1510
1468
|
) : mode === 'single' ? (
|
|
1511
1469
|
<ValueField
|
|
1512
1470
|
{...fieldCommon}
|
|
1513
|
-
strings={
|
|
1471
|
+
strings={shownStrings}
|
|
1514
1472
|
errorStrings={shownStrings}
|
|
1515
1473
|
value={sheetValue}
|
|
1516
1474
|
displayText={precision !== 'day' ? periodText(sheetValue) : undefined}
|
|
@@ -1529,7 +1487,7 @@ const DateTimePicker: React.FC<DateTimePickerProps> = (props) => {
|
|
|
1529
1487
|
) : d.mode === 'range' ? (
|
|
1530
1488
|
<RangeFields
|
|
1531
1489
|
{...fieldCommon}
|
|
1532
|
-
strings={
|
|
1490
|
+
strings={shownStrings}
|
|
1533
1491
|
errorStrings={shownStrings}
|
|
1534
1492
|
layout={rangeLayout}
|
|
1535
1493
|
dateText={draftDateText}
|
|
@@ -1551,7 +1509,7 @@ const DateTimePicker: React.FC<DateTimePickerProps> = (props) => {
|
|
|
1551
1509
|
) : d.mode === 'multiple' ? (
|
|
1552
1510
|
<ValueField
|
|
1553
1511
|
{...fieldCommon}
|
|
1554
|
-
strings={
|
|
1512
|
+
strings={shownStrings}
|
|
1555
1513
|
value={null}
|
|
1556
1514
|
displayText={formatMultipleText(
|
|
1557
1515
|
draftValues,
|
|
@@ -1559,7 +1517,7 @@ const DateTimePicker: React.FC<DateTimePickerProps> = (props) => {
|
|
|
1559
1517
|
dateFormat,
|
|
1560
1518
|
timeFormat,
|
|
1561
1519
|
draftDateText,
|
|
1562
|
-
|
|
1520
|
+
shownStrings
|
|
1563
1521
|
)}
|
|
1564
1522
|
placeholder={sheetOptions?.fieldPlaceholder}
|
|
1565
1523
|
allowClear={allowClear}
|
|
@@ -1578,13 +1536,20 @@ const DateTimePicker: React.FC<DateTimePickerProps> = (props) => {
|
|
|
1578
1536
|
};
|
|
1579
1537
|
const systemA11yLabel =
|
|
1580
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.
|
|
1581
1541
|
const chooseSystem = (next: CalendarSystem) => {
|
|
1582
1542
|
if (next === system) return;
|
|
1543
|
+
edited.current = true;
|
|
1583
1544
|
setChosenSystem(next);
|
|
1584
1545
|
if (next === 'dual') setDualTab(dualDefaultTab);
|
|
1585
1546
|
echo.current = next;
|
|
1586
1547
|
onCalendarSystemChange?.(next);
|
|
1587
1548
|
};
|
|
1549
|
+
const chooseTab = (next: 'gregory' | 'hijri') => {
|
|
1550
|
+
edited.current = true;
|
|
1551
|
+
setDualTab(next);
|
|
1552
|
+
};
|
|
1588
1553
|
const calendarSwitchNode = !switchSystems ? null : renderCalendarSwitch ? (
|
|
1589
1554
|
renderCalendarSwitch({
|
|
1590
1555
|
systems: switchSystems,
|
|
@@ -1616,7 +1581,7 @@ const DateTimePicker: React.FC<DateTimePickerProps> = (props) => {
|
|
|
1616
1581
|
{ value: 'hijri', label: systemLabels.hijri },
|
|
1617
1582
|
]}
|
|
1618
1583
|
selected={dualTab}
|
|
1619
|
-
onSelect={
|
|
1584
|
+
onSelect={chooseTab}
|
|
1620
1585
|
accessibilityLabel={systemA11yLabel}
|
|
1621
1586
|
tokens={tokens}
|
|
1622
1587
|
styles={styles}
|
|
@@ -1632,12 +1597,9 @@ const DateTimePicker: React.FC<DateTimePickerProps> = (props) => {
|
|
|
1632
1597
|
<View style={hijriShown ? RTL : undefined}>{node}</View>
|
|
1633
1598
|
</RtlContext.Provider>
|
|
1634
1599
|
);
|
|
1635
|
-
// Another calendar opens on the days the last one showed, else
|
|
1636
|
-
//
|
|
1637
|
-
const calendarAnchor =
|
|
1638
|
-
shownAnchor.current ??
|
|
1639
|
-
(selectionShown ? null : firstSelectedDay(selection)) ??
|
|
1640
|
-
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;
|
|
1641
1603
|
|
|
1642
1604
|
const calendar = (
|
|
1643
1605
|
<View style={folded ? FOLDED : undefined}>
|
|
@@ -1646,10 +1608,10 @@ const DateTimePicker: React.FC<DateTimePickerProps> = (props) => {
|
|
|
1646
1608
|
{mirrored(
|
|
1647
1609
|
<Calendar
|
|
1648
1610
|
key={shownSystem}
|
|
1649
|
-
constraints={
|
|
1611
|
+
constraints={calendarConstraints}
|
|
1650
1612
|
today={today}
|
|
1651
1613
|
initialVisibleDate={calendarAnchor}
|
|
1652
|
-
selection={
|
|
1614
|
+
selection={selection}
|
|
1653
1615
|
onSelectDay={onSelectDayStable}
|
|
1654
1616
|
firstDayOfWeek={firstDayOfWeek}
|
|
1655
1617
|
showOutsideDays={showOutsideDays}
|
|
@@ -1725,14 +1687,11 @@ const DateTimePicker: React.FC<DateTimePickerProps> = (props) => {
|
|
|
1725
1687
|
? validation.message
|
|
1726
1688
|
: null;
|
|
1727
1689
|
|
|
1728
|
-
// The field
|
|
1729
|
-
// 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.
|
|
1730
1691
|
const sheetContent = (
|
|
1731
1692
|
<>
|
|
1732
|
-
<NumeralsContext.Provider value={digitsFor(draftSystem)}>
|
|
1733
|
-
{sheetField}
|
|
1734
|
-
</NumeralsContext.Provider>
|
|
1735
1693
|
<NumeralsContext.Provider value={digitsFor(shownSystem)}>
|
|
1694
|
+
{sheetField}
|
|
1736
1695
|
{calendar}
|
|
1737
1696
|
</NumeralsContext.Provider>
|
|
1738
1697
|
{timeRow == null ? null : (
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import React, { memo } from 'react';
|
|
2
|
-
import { Pressable, Text, View } from 'react-native';
|
|
2
|
+
import { Pressable, StyleSheet, Text, View } from 'react-native';
|
|
3
3
|
import { isSameDay } from '../../core/calendarDate';
|
|
4
4
|
import type { CalendarDate } from '../../core/types';
|
|
5
5
|
import type { PickerStyles } from '../theme/styleKeys';
|
|
@@ -35,9 +35,10 @@ interface Props extends DayCellState {
|
|
|
35
35
|
}
|
|
36
36
|
|
|
37
37
|
/**
|
|
38
|
-
* One day: today ring
|
|
39
|
-
* disabled, holiday colour. Only
|
|
40
|
-
* memo keeps typing and taps
|
|
38
|
+
* One day: today ring (faded when today is disabled), selected fill,
|
|
39
|
+
* range band, greyed (not pressable) when disabled, holiday colour. Only
|
|
40
|
+
* primitive-ish props change between renders, so memo keeps typing and taps
|
|
41
|
+
* from redrawing all 42 cells.
|
|
41
42
|
*/
|
|
42
43
|
const DayCell: React.FC<Props> = ({
|
|
43
44
|
date,
|
|
@@ -72,6 +73,12 @@ const DayCell: React.FC<Props> = ({
|
|
|
72
73
|
: rangeRole === 'end'
|
|
73
74
|
? s.fillEnd
|
|
74
75
|
: null;
|
|
76
|
+
// The today ring. On a greyed today it's faded, in the day's shape.
|
|
77
|
+
const ring = isToday && !filled;
|
|
78
|
+
const fadedRing = ring && isDisabled;
|
|
79
|
+
const dayRadius = fadedRing
|
|
80
|
+
? StyleSheet.flatten(styles?.day)?.borderRadius
|
|
81
|
+
: undefined;
|
|
75
82
|
|
|
76
83
|
return (
|
|
77
84
|
<View style={s.cellWrap}>
|
|
@@ -87,7 +94,7 @@ const DayCell: React.FC<Props> = ({
|
|
|
87
94
|
style={({ pressed }) => [
|
|
88
95
|
s.day,
|
|
89
96
|
styles?.day,
|
|
90
|
-
|
|
97
|
+
ring && !isDisabled && [s.today, styles?.today],
|
|
91
98
|
filled && [s.selected, styles?.selected],
|
|
92
99
|
rangeRole === 'start' && styles?.rangeStart,
|
|
93
100
|
rangeRole === 'middle' && styles?.rangeMiddle,
|
|
@@ -98,6 +105,18 @@ const DayCell: React.FC<Props> = ({
|
|
|
98
105
|
pressed && !isDisabled && !filled && [s.pressed, styles?.dayPressed],
|
|
99
106
|
]}
|
|
100
107
|
>
|
|
108
|
+
{fadedRing ? (
|
|
109
|
+
<View
|
|
110
|
+
pointerEvents="none"
|
|
111
|
+
testID={testID ? `${testID}-today-ring` : undefined}
|
|
112
|
+
style={[
|
|
113
|
+
s.today,
|
|
114
|
+
styles?.today,
|
|
115
|
+
s.todayDisabledRing,
|
|
116
|
+
dayRadius !== undefined && { borderRadius: dayRadius },
|
|
117
|
+
]}
|
|
118
|
+
/>
|
|
119
|
+
) : null}
|
|
101
120
|
<Text
|
|
102
121
|
maxFontSizeMultiplier={MAX_FONT_SCALE}
|
|
103
122
|
style={[
|
|
@@ -107,7 +126,7 @@ const DayCell: React.FC<Props> = ({
|
|
|
107
126
|
isHoliday &&
|
|
108
127
|
showHolidays &&
|
|
109
128
|
!filled && [s.holidayText, styles?.holidayText],
|
|
110
|
-
|
|
129
|
+
ring && styles?.todayText,
|
|
111
130
|
isDisabled && [s.disabledText, styles?.disabledText],
|
|
112
131
|
filled && [s.selectedText, styles?.selectedText],
|
|
113
132
|
]}
|
|
@@ -97,6 +97,17 @@ const create = (t: ThemeTokens) =>
|
|
|
97
97
|
color: t.mutedText,
|
|
98
98
|
},
|
|
99
99
|
today: { borderWidth: 1, borderColor: t.accent },
|
|
100
|
+
// A greyed today: its accent ring, faded. A layer of its own, so the fade
|
|
101
|
+
// leaves the day number as grey as the other greyed days.
|
|
102
|
+
todayDisabledRing: {
|
|
103
|
+
position: 'absolute',
|
|
104
|
+
top: 0,
|
|
105
|
+
right: 0,
|
|
106
|
+
bottom: 0,
|
|
107
|
+
left: 0,
|
|
108
|
+
borderRadius: t.radius,
|
|
109
|
+
opacity: 0.4,
|
|
110
|
+
},
|
|
100
111
|
selected: { backgroundColor: t.accent },
|
|
101
112
|
selectedText: { color: t.onAccent, ...fontStyle(t, 'semiBold') },
|
|
102
113
|
outsideText: { color: t.outsideText ?? t.mutedText },
|
|
@@ -21,6 +21,7 @@ import FieldMessages from './FieldMessages';
|
|
|
21
21
|
import {
|
|
22
22
|
formatValueParts,
|
|
23
23
|
formatValueText,
|
|
24
|
+
emptyFieldParts,
|
|
24
25
|
placeholderText,
|
|
25
26
|
segmentErrorText,
|
|
26
27
|
type DateTextFormatter,
|
|
@@ -240,6 +241,20 @@ const ValueField: React.FC<ValueFieldProps> = (props) => {
|
|
|
240
241
|
styles?.fieldText,
|
|
241
242
|
disabled && [s.disabledText, styles?.fieldTextDisabled],
|
|
242
243
|
];
|
|
244
|
+
// The empty field's placeholder sets its date apart from its time the same way.
|
|
245
|
+
const separator = strings.dateTimeSeparator ?? ' ';
|
|
246
|
+
const emptyParts =
|
|
247
|
+
styles?.fieldDateText && type === 'datetime'
|
|
248
|
+
? emptyFieldParts(placeholder, type, dateFormat, timeFormat, strings)
|
|
249
|
+
: null;
|
|
250
|
+
const placeholderSplit = emptyParts?.time ? emptyParts : null;
|
|
251
|
+
const placeholderStyle = [
|
|
252
|
+
s.text,
|
|
253
|
+
styles?.fieldText,
|
|
254
|
+
s.placeholder,
|
|
255
|
+
styles?.fieldPlaceholder,
|
|
256
|
+
disabled && s.disabledText,
|
|
257
|
+
];
|
|
243
258
|
|
|
244
259
|
const clear = () => {
|
|
245
260
|
dispatch({ type: 'reset', value: null });
|
|
@@ -335,7 +350,7 @@ const ValueField: React.FC<ValueFieldProps> = (props) => {
|
|
|
335
350
|
ellipsizeMode="clip"
|
|
336
351
|
style={[valueStyle, s.textScrolled, styles?.fieldDateText]}
|
|
337
352
|
>
|
|
338
|
-
{digits(`${parts.date}${
|
|
353
|
+
{digits(`${parts.date}${separator}`)}
|
|
339
354
|
</Text>
|
|
340
355
|
<Text numberOfLines={1} ellipsizeMode="clip" style={valueStyle}>
|
|
341
356
|
{digits(parts.time)}
|
|
@@ -366,17 +381,21 @@ const ValueField: React.FC<ValueFieldProps> = (props) => {
|
|
|
366
381
|
{digits(text)}
|
|
367
382
|
</Text>
|
|
368
383
|
</ScrollView>
|
|
384
|
+
) : placeholderSplit ? (
|
|
385
|
+
<>
|
|
386
|
+
<Text
|
|
387
|
+
numberOfLines={1}
|
|
388
|
+
ellipsizeMode="clip"
|
|
389
|
+
style={[placeholderStyle, s.textScrolled, styles?.fieldDateText]}
|
|
390
|
+
>
|
|
391
|
+
{`${placeholderSplit.date}${separator}`}
|
|
392
|
+
</Text>
|
|
393
|
+
<Text numberOfLines={1} ellipsizeMode="clip" style={placeholderStyle}>
|
|
394
|
+
{placeholderSplit.time}
|
|
395
|
+
</Text>
|
|
396
|
+
</>
|
|
369
397
|
) : (
|
|
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
|
-
>
|
|
398
|
+
<Text numberOfLines={1} style={placeholderStyle}>
|
|
380
399
|
{placeholder ||
|
|
381
400
|
placeholderText(type, dateFormat, timeFormat, strings)}
|
|
382
401
|
</Text>
|