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.
Files changed (57) hide show
  1. package/CHANGELOG.md +21 -0
  2. package/lib/commonjs/components/DateTimePicker.js +67 -115
  3. package/lib/commonjs/components/DateTimePicker.js.map +1 -1
  4. package/lib/commonjs/components/calendar/DayCell.js +17 -6
  5. package/lib/commonjs/components/calendar/DayCell.js.map +1 -1
  6. package/lib/commonjs/components/calendar/calendarStyles.js +11 -0
  7. package/lib/commonjs/components/calendar/calendarStyles.js.map +1 -1
  8. package/lib/commonjs/components/field/ValueField.js +19 -2
  9. package/lib/commonjs/components/field/ValueField.js.map +1 -1
  10. package/lib/commonjs/components/field/fieldText.js +44 -5
  11. package/lib/commonjs/components/field/fieldText.js.map +1 -1
  12. package/lib/commonjs/components/theme/styleKeys.js +3 -1
  13. package/lib/commonjs/components/theme/styleKeys.js.map +1 -1
  14. package/lib/commonjs/components/types.js +2 -1
  15. package/lib/commonjs/components/types.js.map +1 -1
  16. package/lib/module/components/DateTimePicker.js +68 -116
  17. package/lib/module/components/DateTimePicker.js.map +1 -1
  18. package/lib/module/components/calendar/DayCell.js +18 -7
  19. package/lib/module/components/calendar/DayCell.js.map +1 -1
  20. package/lib/module/components/calendar/calendarStyles.js +11 -0
  21. package/lib/module/components/calendar/calendarStyles.js.map +1 -1
  22. package/lib/module/components/field/ValueField.js +20 -3
  23. package/lib/module/components/field/ValueField.js.map +1 -1
  24. package/lib/module/components/field/fieldText.js +40 -4
  25. package/lib/module/components/field/fieldText.js.map +1 -1
  26. package/lib/module/components/theme/styleKeys.js +3 -1
  27. package/lib/module/components/theme/styleKeys.js.map +1 -1
  28. package/lib/module/components/types.js +2 -1
  29. package/lib/module/components/types.js.map +1 -1
  30. package/lib/typescript/commonjs/src/components/DateTimePicker.d.ts.map +1 -1
  31. package/lib/typescript/commonjs/src/components/calendar/DayCell.d.ts.map +1 -1
  32. package/lib/typescript/commonjs/src/components/calendar/calendarStyles.d.ts +9 -0
  33. package/lib/typescript/commonjs/src/components/calendar/calendarStyles.d.ts.map +1 -1
  34. package/lib/typescript/commonjs/src/components/field/ValueField.d.ts.map +1 -1
  35. package/lib/typescript/commonjs/src/components/field/fieldText.d.ts +19 -0
  36. package/lib/typescript/commonjs/src/components/field/fieldText.d.ts.map +1 -1
  37. package/lib/typescript/commonjs/src/components/theme/styleKeys.d.ts.map +1 -1
  38. package/lib/typescript/commonjs/src/components/types.d.ts +18 -5
  39. package/lib/typescript/commonjs/src/components/types.d.ts.map +1 -1
  40. package/lib/typescript/module/src/components/DateTimePicker.d.ts.map +1 -1
  41. package/lib/typescript/module/src/components/calendar/DayCell.d.ts.map +1 -1
  42. package/lib/typescript/module/src/components/calendar/calendarStyles.d.ts +9 -0
  43. package/lib/typescript/module/src/components/calendar/calendarStyles.d.ts.map +1 -1
  44. package/lib/typescript/module/src/components/field/ValueField.d.ts.map +1 -1
  45. package/lib/typescript/module/src/components/field/fieldText.d.ts +19 -0
  46. package/lib/typescript/module/src/components/field/fieldText.d.ts.map +1 -1
  47. package/lib/typescript/module/src/components/theme/styleKeys.d.ts.map +1 -1
  48. package/lib/typescript/module/src/components/types.d.ts +18 -5
  49. package/lib/typescript/module/src/components/types.d.ts.map +1 -1
  50. package/package.json +1 -1
  51. package/src/components/DateTimePicker.tsx +79 -120
  52. package/src/components/calendar/DayCell.tsx +25 -6
  53. package/src/components/calendar/calendarStyles.ts +11 -0
  54. package/src/components/field/ValueField.tsx +30 -11
  55. package/src/components/field/fieldText.ts +48 -5
  56. package/src/components/theme/styleKeys.ts +3 -1
  57. package/src/components/types.ts +18 -5
package/CHANGELOG.md CHANGED
@@ -4,6 +4,22 @@ All notable changes to this project are documented here. The format follows [Kee
4
4
 
5
5
  ## [Unreleased]
6
6
 
7
+ ## [1.3.1] - 2026-11-13
8
+
9
+ ### Added
10
+
11
+ - `sheet.showNotAllowedError` (default `true`): `false` hides "This date isn't available" above the calendar when the picked date (or a range's end) is a disabled day, such as a saved value the rules no longer allow. Confirm stays off for that day either way.
12
+
13
+ ### Fixed
14
+
15
+ - A greyed (disabled) today no longer keeps the full accent ring. Its ring keeps the accent colour at 40% opacity, like the other greyed controls, and the day number stays as grey as the other greyed days. A custom `styles.today` (its border colour or width, for example) and the corner radius from `styles.day` still apply to that ring.
16
+ - With a `fieldDateText` style, the empty date-time field's placeholder now sets its date apart from its time, like the value does, so `{ minWidth: '50%' }` starts the placeholder's time at the middle of the field too. A `placeholder` passed in is cut at the first `strings.dateTimeSeparator` when what follows is a time (`'DD/MM/YYYY HH:MM'`); wording such as `'Pick a time'` stays whole.
17
+
18
+ ### Changed
19
+
20
+ - **A dual calendar's selection shows in both tabs.** A day picked on the Gregorian tab is highlighted on the Hijri tab too, and the other way round, and a tap on either tab edits the same selection instead of starting over, so a range can start on one tab and end on the other. The selection is saved with the tab on screen: Confirm on the Hijri tab saves a Gregorian pick as Hijri (`info.calendarSystem: 'hijri'`), and the field writes it that way. With `commitMode="instant"`, a tab (or a calendar in the switch) the user picks saves the selection again with that calendar; the tab the picker opens on doesn't. The field inside the sheet now follows the tab too. A new `calendarSystem` prop no longer clears a selection in progress. Values stay Gregorian.
21
+ - **A dual calendar reopens on the tab its value was saved with.** A date saved from the Hijri tab opens the picker on the Hijri tab next time, while the picker stays mounted. With no value it still opens on `dualDefaultTab`, and so does a value passed in from outside: to keep the tab after a remount, store `info.calendarSystem` and pass it as `dualDefaultTab`.
22
+
7
23
  ## [1.3.0] - 2026-11-12
8
24
 
9
25
  ### Added
@@ -160,6 +176,11 @@ Initial release.
160
176
  - `dateFormat` in any case (`'dd/mm/yyyy'`). A format the picker can't use warns in development and falls back to the locale order.
161
177
  - Accessibility labels and states, reduce-motion support, a font-scale cap and RTL mirroring. On iOS every typed box gets a Done button above the number pad, and errors are announced to VoiceOver.
162
178
 
179
+ [Unreleased]: https://github.com/Bhairavi-ancy/rn-grid-date-time-picker/compare/v1.3.1...HEAD
180
+ [1.3.1]: https://github.com/Bhairavi-ancy/rn-grid-date-time-picker/compare/v1.3.0...v1.3.1
181
+ [1.3.0]: https://github.com/Bhairavi-ancy/rn-grid-date-time-picker/compare/v1.2.2...v1.3.0
182
+ [1.2.2]: https://github.com/Bhairavi-ancy/rn-grid-date-time-picker/compare/v1.2.1...v1.2.2
183
+ [1.2.1]: https://github.com/Bhairavi-ancy/rn-grid-date-time-picker/compare/v1.2.0...v1.2.1
163
184
  [1.2.0]: https://github.com/Bhairavi-ancy/rn-grid-date-time-picker/compare/v1.1.0...v1.2.0
164
185
  [1.1.0]: https://github.com/Bhairavi-ancy/rn-grid-date-time-picker/compare/v1.0.0...v1.1.0
165
186
  [1.0.0]: https://github.com/Bhairavi-ancy/rn-grid-date-time-picker/releases/tag/v1.0.0
@@ -21,7 +21,6 @@ var _CalendarSwitch = _interopRequireDefault(require("./calendar/CalendarSwitch.
21
21
  var _localeNames = require("./calendar/localeNames.js");
22
22
  var _direction = require("./theme/direction.js");
23
23
  var _numerals2 = require("./theme/numerals.js");
24
- var _selection = require("./calendar/selection.js");
25
24
  var _PickerContainer = _interopRequireDefault(require("./container/PickerContainer.js"));
26
25
  var _fieldText = require("./field/fieldText.js");
27
26
  var _FieldMessages = _interopRequireDefault(require("./field/FieldMessages.js"));
@@ -46,19 +45,6 @@ const RTL = {
46
45
  direction: 'rtl'
47
46
  };
48
47
 
49
- /** The same kind of selection with nothing in it. */
50
- const emptySelection = sel => sel.kind === 'single' ? {
51
- kind: 'single',
52
- day: null
53
- } : sel.kind === 'range' ? {
54
- kind: 'range',
55
- from: null,
56
- to: null
57
- } : {
58
- ...sel,
59
- days: []
60
- };
61
-
62
48
  /** Minute steps that divide the hour. */
63
49
  const MINUTE_STEPS = [1, 2, 3, 4, 5, 6, 10, 12, 15, 20, 30, 60];
64
50
 
@@ -575,10 +561,10 @@ const DateTimePicker = props => {
575
561
  };
576
562
  const valueKey = JSON.stringify(value ?? '');
577
563
 
578
- // A selection belongs to the calendar it was picked in (Gregorian or Hijri):
579
- // it shows only there, and the field writes it in that calendar. A saved
580
- // value remembers its calendar; one from outside counts as picked in the
581
- // calendar the picker opens on.
564
+ // A selection shows in both tabs (Gregorian and Hijri). It is saved with the
565
+ // calendar on screen, and the on-screen field writes it in that calendar. A
566
+ // saved value remembers its calendar; one from outside counts as picked in
567
+ // the calendar the picker opens on.
582
568
  // The saved value as a draft, for the on-screen field and for comparing
583
569
  // values by what they hold.
584
570
  const saved = (0, _react.useMemo)(() => (0, _draft.createDraft)(mode, value, draftOptions),
@@ -589,32 +575,12 @@ const DateTimePicker = props => {
589
575
  const savedContent = contentOf(saved);
590
576
  const savedSystemRef = (0, _react.useRef)(null);
591
577
  const savedSystem = savedSystemRef.current?.content === savedContent ? savedSystemRef.current.system : outsideSystem;
592
- const [draftSystem, setDraftSystem] = (0, _react.useState)(savedSystem);
593
- // The draft is rebuilt on open and on a new value; so is its calendar. A new
594
- // calendarSystem prop while the user is picking starts the selection over,
595
- // since a selection is one calendar's.
596
- const systemSeen = (0, _react.useRef)({
597
- isOpen,
598
- valueKey,
599
- savedSystem
600
- });
601
- (0, _react.useEffect)(() => {
602
- const seen = systemSeen.current;
603
- systemSeen.current = {
604
- isOpen,
605
- valueKey,
606
- savedSystem
607
- };
608
- setDraftSystem(savedSystem);
609
- const systemOnly = seen.isOpen === isOpen && seen.valueKey === valueKey && seen.savedSystem !== savedSystem;
610
- if (systemOnly && isOpen && edited.current) {
611
- draft.clear();
612
- resetSelectionUi();
613
- }
614
- // eslint-disable-next-line react-hooks/exhaustive-deps -- the draft's actions are stable
615
- }, [isOpen, valueKey, savedSystem]);
616
- (0, _react.useEffect)(() => setDualTab(dualDefaultTab), [isOpen, dualDefaultTab]);
617
- const selectionShown = draftSystem === shownSystem;
578
+ const hasSaved = valuesOf(saved).some(v => v !== null);
579
+ // Each open starts on the tab the saved value was saved with; with no value,
580
+ // on dualDefaultTab.
581
+ (0, _react.useEffect)(() => setDualTab(hasSaved ? savedSystem : dualDefaultTab),
582
+ // eslint-disable-next-line react-hooks/exhaustive-deps -- on open and on a new dualDefaultTab, not on every save
583
+ [isOpen, dualDefaultTab]);
618
584
  /** Saves a value: with the calendar it was picked in, when one can be Hijri. */
619
585
  const emitChange = (0, _react.useCallback)((wire, picked) => {
620
586
  savedSystemRef.current = {
@@ -638,11 +604,12 @@ const DateTimePicker = props => {
638
604
  bounds: constraints.timeBounds(v.date)
639
605
  }) === null)) && !typingBlocked && !wheelSpinning && !confirmDisabled;
640
606
 
641
- // Every save: Confirm, an 'instant' edit (onConfirm is added by `confirm`).
607
+ // Every save: Confirm, an 'instant' edit (onConfirm is added by `confirm`),
608
+ // with the calendar on screen.
642
609
  const commit = (0, _react.useCallback)(wire => {
643
- emitChange(wire, draftSystem);
610
+ emitChange(wire, shownSystem);
644
611
  onBlur?.();
645
- }, [emitChange, draftSystem, onBlur]);
612
+ }, [emitChange, shownSystem, onBlur]);
646
613
  const confirm = (0, _react.useCallback)(() => {
647
614
  if (!canConfirm) return;
648
615
  const {
@@ -674,8 +641,8 @@ const DateTimePicker = props => {
674
641
  draft.clear();
675
642
  resetSelectionUi();
676
643
  edited.current = false;
677
- emitChange('', draftSystem);
678
- }, [draft, emitChange, draftSystem, resetSelectionUi]);
644
+ emitChange('', shownSystem);
645
+ }, [draft, emitChange, shownSystem, resetSelectionUi]);
679
646
  const clearDraft = (0, _react.useCallback)(() => {
680
647
  edited.current = true;
681
648
  draft.clear();
@@ -684,7 +651,6 @@ const DateTimePicker = props => {
684
651
 
685
652
  // × in the field above the calendar. 'instant' saves every edit, so it
686
653
  // clears the saved value too (once; an empty value isn't saved again).
687
- const hasSaved = valuesOf(saved).some(v => v !== null);
688
654
  const clearInSheet = (0, _react.useCallback)(() => {
689
655
  if (commitMode === 'instant' && hasSaved) clearValue();else clearDraft();
690
656
  }, [commitMode, hasSaved, clearValue, clearDraft]);
@@ -696,7 +662,8 @@ const DateTimePicker = props => {
696
662
  // Confirm press, but only while open (closing discards, as with Confirm),
697
663
  // and only what the draft holds: a time still being typed isn't saved yet.
698
664
  // An edit equal to the saved value isn't saved again; one `validate`
699
- // rejected waits until the selection or the saved value changes.
665
+ // rejected waits until the selection or the saved value changes. A tab
666
+ // change the user makes saves the selection again with the new calendar.
700
667
  const lastRejected = (0, _react.useRef)(null);
701
668
  // The last value saved: not saved again while the parent's `value` catches
702
669
  // up (a slow store, a server round trip) or when it never does.
@@ -714,11 +681,12 @@ const DateTimePicker = props => {
714
681
  if (!typingDone || timeIssue || !canSave(draft.draft)) return;
715
682
  const wire = draft.toWire();
716
683
  const key = JSON.stringify(wire);
717
- if (key === lastRejected.current || key === lastEmitted.current) return;
718
- // The same dates picked in the same calendar aren't saved again; the same
719
- // dates picked in the other calendar are.
684
+ const sent = `${shownSystem} ${key}`;
685
+ if (key === lastRejected.current || sent === lastEmitted.current) return;
686
+ // The same dates in the same calendar aren't saved again; the same dates
687
+ // with the other tab on screen are.
720
688
  const unchanged = key === valueKey || contentOf(draft.draft) === savedContent;
721
- if (unchanged && draftSystem === savedSystem) return;
689
+ if (unchanged && shownSystem === savedSystem) return;
722
690
  const message = latest.current.validate?.(wire);
723
691
  if (message) {
724
692
  lastRejected.current = key;
@@ -729,14 +697,14 @@ const DateTimePicker = props => {
729
697
  latest.current.onError?.('VALIDATION', wire);
730
698
  return;
731
699
  }
732
- lastEmitted.current = key;
700
+ lastEmitted.current = sent;
733
701
  commit(wire);
734
702
  };
735
703
  // Only typing waits for the field to be left; a tap is a finished edit.
736
704
  const typedLast = (0, _react.useRef)(false);
737
705
  (0, _react.useEffect)(() => saveInstant(!sheetTyping || !typedLast.current),
738
706
  // eslint-disable-next-line react-hooks/exhaustive-deps -- when the draft or what blocks it changes, not when callbacks are re-created
739
- [commitMode, isOpen, sheetTyping, draft.draft, draftSystem, timeIssue, typingBlocked, wheelSpinning, confirmDisabled, constraints]);
707
+ [commitMode, isOpen, sheetTyping, draft.draft, shownSystem, timeIssue, typingBlocked, wheelSpinning, confirmDisabled, constraints]);
740
708
  // Closing while the sheet's field still has focus ends the typing: an
741
709
  // instant picker saves what was typed before it closes.
742
710
  const saveInstantRef = (0, _react.useRef)(saveInstant);
@@ -752,16 +720,9 @@ const DateTimePicker = props => {
752
720
  date: parsed.dateOnly,
753
721
  time: fixedTime ?? pending[key] ?? newTime()
754
722
  } : null);
755
-
756
- // Typed segments are Gregorian, so typing keeps the calendar a selection was
757
- // picked in; into an empty draft, it takes the calendar on screen.
758
- const adoptShownIfEmpty = () => {
759
- if (valuesOf(d).every(v => v === null)) setDraftSystem(shownSystem);
760
- };
761
723
  const onSheetTyped = parsed => {
762
724
  edited.current = true;
763
725
  typedLast.current = true;
764
- adoptShownIfEmpty();
765
726
  const typed = withFilledTime(parsed, 'single');
766
727
  setTypingBlock('single', !parsed.isEmpty && !typed);
767
728
  if (typed) draft.setValue(typed, 'single');else if (parsed.isEmpty) draft.clear();
@@ -769,7 +730,6 @@ const DateTimePicker = props => {
769
730
  const onRangeTyped = (end, parsed) => {
770
731
  edited.current = true;
771
732
  typedLast.current = true;
772
- adoptShownIfEmpty();
773
733
  const typed = withFilledTime(parsed, end);
774
734
  setTypingBlock(end, !parsed.isEmpty && !typed);
775
735
  if (typed || parsed.isEmpty) draft.setValue(typed, end);
@@ -815,56 +775,45 @@ const DateTimePicker = props => {
815
775
  const onSelectDay = (0, _react.useCallback)(day => {
816
776
  edited.current = true;
817
777
  typedLast.current = false;
818
- // A pick in the other calendar starts over: the selection is one calendar's.
819
- const fresh = draftSystem !== shownSystem && valuesOf(d).some(v => v !== null);
820
- setDraftSystem(shownSystem);
821
- const cur = fresh ? (0, _draft.clearDraft)(d) : d;
822
- const end0 = fresh ? 'from' : activeEnd;
823
- const dayInEdit = fresh ? null : activeDay;
824
- const times = fresh ? {} : pending;
825
- if (fresh) {
826
- draft.clear();
827
- resetSelectionUi();
828
- }
829
778
  // A multiple picker scrolls for its first date only, so more can be picked.
830
- if (cur.mode !== 'multiple' || cur.slots.length === 0) revealTime();
831
- if (cur.mode === 'range') {
779
+ if (d.mode !== 'multiple' || d.slots.length === 0) revealTime();
780
+ if (d.mode === 'range') {
832
781
  if (rangeLayout === 'fromTo') {
833
782
  setEndPicked(false);
834
783
  // A full range: the tap starts a new one (or moves the nearest end).
835
- if (!endPicked && cur.from.value && cur.to.value) {
784
+ if (!endPicked && d.from.value && d.to.value) {
836
785
  const moveNearest = range?.reselect === 'moveNearestEnd';
837
- const nearest = Math.abs((0, _calendarDate.daysBetween)(cur.from.value.date, day)) < Math.abs((0, _calendarDate.daysBetween)(cur.to.value.date, day)) ? 'from' : 'to';
786
+ const nearest = Math.abs((0, _calendarDate.daysBetween)(d.from.value.date, day)) < Math.abs((0, _calendarDate.daysBetween)(d.to.value.date, day)) ? 'from' : 'to';
838
787
  const end = moveNearest ? nearest : 'from';
839
788
  setTypingBlock('from', false);
840
789
  setTypingBlock('to', false);
841
- draft.selectDay(day, fixedTime ?? times[end] ?? newTime());
790
+ draft.selectDay(day, fixedTime ?? pending[end] ?? newTime());
842
791
  setActiveEnd(moveNearest ? nearest : 'to');
843
792
  return;
844
793
  }
845
794
  // Choosing the end after a start tap: a day before the start moves the start.
846
- const end1 = end0 === 'to' && !endPicked && cur.from.value && (0, _calendarDate.compareCalendarDates)(day, cur.from.value.date) < 0 ? 'from' : end0;
795
+ const end1 = activeEnd === 'to' && !endPicked && d.from.value && (0, _calendarDate.compareCalendarDates)(day, d.from.value.date) < 0 ? 'from' : activeEnd;
847
796
  // The tapped day replaces whatever was typed in this end only.
848
797
  setTypingBlock(end1, false);
849
- draft.setRangeEnd(day, end1, end1 === 'to' ? endTimeFor(cur.from.value, day, times.to) : fixedTime ?? times.from ?? newTime());
798
+ draft.setRangeEnd(day, end1, end1 === 'to' ? endTimeFor(d.from.value, day, pending.to) : fixedTime ?? pending.from ?? newTime());
850
799
  // The start tapped again where a range can't be one day deselects
851
800
  // it, so the next tap picks the start again.
852
- const deselects = end1 === 'to' && !(0, _draft.sameDayRangeOk)(draftOptions) && (0, _calendarDate.isSameDay)(cur.from.value?.date, day);
853
- if (end0 === 'from') setActiveEnd('to');else if (deselects) setActiveEnd('from');
801
+ const deselects = end1 === 'to' && !(0, _draft.sameDayRangeOk)(draftOptions) && (0, _calendarDate.isSameDay)(d.from.value?.date, day);
802
+ if (activeEnd === 'from') setActiveEnd('to');else if (deselects) setActiveEnd('from');
854
803
  return;
855
804
  }
856
805
  // The single field: the first tap is the start, the next one the end.
857
- const startsOver = !cur.from.value || !!cur.to.value && range?.reselect !== 'moveNearestEnd';
806
+ const startsOver = !d.from.value || !!d.to.value && range?.reselect !== 'moveNearestEnd';
858
807
  const end = startsOver ? 'from' : 'to';
859
808
  setTypingBlock('single', false);
860
- draft.selectDay(day, end === 'to' && !cur.to.value ? endTimeFor(cur.from.value, day, times.to) : fixedTime ?? times[end] ?? newTime());
809
+ draft.selectDay(day, end === 'to' && !d.to.value ? endTimeFor(d.from.value, day, pending.to) : fixedTime ?? pending[end] ?? newTime());
861
810
  return;
862
811
  }
863
- if (cur.mode === 'multiple') {
812
+ if (d.mode === 'multiple') {
864
813
  const perDate = type === 'datetime' && multipleTimeMode === 'perDate';
865
- const picked = cur.slots.some(slot => (0, _calendarDate.isSameDay)(slot.value?.date, day));
814
+ const picked = d.slots.some(slot => (0, _calendarDate.isSameDay)(slot.value?.date, day));
866
815
  // perDate: a tap on a picked day first makes it the one the time row edits.
867
- if (perDate && picked && !(0, _calendarDate.isSameDay)(dayInEdit, day)) {
816
+ if (perDate && picked && !(0, _calendarDate.isSameDay)(activeDay, day)) {
868
817
  setActiveDay(day);
869
818
  return;
870
819
  }
@@ -873,10 +822,10 @@ const DateTimePicker = props => {
873
822
  return;
874
823
  }
875
824
  setTypingBlock('single', false);
876
- draft.selectDay(day, fixedTime ?? times.single ?? newTime());
825
+ draft.selectDay(day, fixedTime ?? pending.single ?? newTime());
877
826
  },
878
827
  // eslint-disable-next-line react-hooks/exhaustive-deps -- valuesOf reads only `d` and props listed here
879
- [d, draft, newTime, rangeLayout, activeEnd, activeDay, fixedTime, pending, range?.reselect, type, multipleTimeMode, setTypingBlock, draftSystem, shownSystem, resetSelectionUi, endPicked, draftOptions, minuteStep, constraints, revealTime]);
828
+ [d, draft, newTime, rangeLayout, activeEnd, activeDay, fixedTime, pending, range?.reselect, type, multipleTimeMode, setTypingBlock, endPicked, draftOptions, minuteStep, constraints, revealTime]);
880
829
 
881
830
  // The calendar gets a tap handler that never changes, so its memoized day
882
831
  // cells don't all redraw on every tap; it calls the current one.
@@ -999,11 +948,10 @@ const DateTimePicker = props => {
999
948
  // The calendar the on-screen field writes the saved value in.
1000
949
  const fieldSystem = fieldCalendar === 'gregory' ? 'gregory' : savedSystem;
1001
950
  const savedDateText = dateTextFor(fieldSystem);
1002
- const draftDateText = dateTextFor(draftSystem);
951
+ const draftDateText = dateTextFor(shownSystem);
1003
952
  const savedStrings = stringsFor(fieldSystem);
1004
- const draftStrings = stringsFor(draftSystem);
1005
- // Error messages inside the picker follow the tab on screen; the on-screen
1006
- // field's keep `strings`.
953
+ // The texts inside the picker, its error messages too, follow the tab on
954
+ // screen; the on-screen field's keep `strings`.
1007
955
  const shownStrings = stringsFor(shownSystem);
1008
956
 
1009
957
  // What the on-screen field shows: the saved value, never the draft.
@@ -1055,11 +1003,12 @@ const DateTimePicker = props => {
1055
1003
  };
1056
1004
  const sheetValue = draft.draft.mode === 'single' ? draft.draft.slot.value : null;
1057
1005
  // A time outside the limits is explained under the time row, not here.
1058
- const sheetValueError = sheetValue && dayOff(sheetValue.date) ? shownStrings.errorNotAllowed : '';
1006
+ const notAllowedError = sheetOptions?.showNotAllowedError === false ? '' : shownStrings.errorNotAllowed;
1007
+ const sheetValueError = sheetValue && dayOff(sheetValue.date) ? notAllowedError : '';
1059
1008
  let rangeError = '';
1060
1009
  if (d.mode === 'range' && d.from.value && d.to.value) {
1061
1010
  const length = (0, _calendarDate.daysBetween)(d.from.value.date, d.to.value.date) + 1;
1062
- if (constraints.dayStatus(d.from.value.date).disabled || constraints.dayStatus(d.to.value.date).disabled) rangeError = shownStrings.errorNotAllowed;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);
1011
+ 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);
1063
1012
  }
1064
1013
  const fieldCommon = {
1065
1014
  type,
@@ -1109,7 +1058,7 @@ const DateTimePicker = props => {
1109
1058
  testID: testID ? `${testID}-value` : undefined
1110
1059
  }) : mode === 'single' ? /*#__PURE__*/(0, _jsxRuntime.jsx)(_ValueField.default, {
1111
1060
  ...fieldCommon,
1112
- strings: draftStrings,
1061
+ strings: shownStrings,
1113
1062
  errorStrings: shownStrings,
1114
1063
  value: sheetValue,
1115
1064
  displayText: precision !== 'day' ? periodText(sheetValue) : undefined,
@@ -1126,7 +1075,7 @@ const DateTimePicker = props => {
1126
1075
  testID: testID ? `${testID}-value` : undefined
1127
1076
  }) : d.mode === 'range' ? /*#__PURE__*/(0, _jsxRuntime.jsx)(_RangeFields.default, {
1128
1077
  ...fieldCommon,
1129
- strings: draftStrings,
1078
+ strings: shownStrings,
1130
1079
  errorStrings: shownStrings,
1131
1080
  layout: rangeLayout,
1132
1081
  dateText: draftDateText,
@@ -1146,9 +1095,9 @@ const DateTimePicker = props => {
1146
1095
  testID: testID
1147
1096
  }) : d.mode === 'multiple' ? /*#__PURE__*/(0, _jsxRuntime.jsx)(_ValueField.default, {
1148
1097
  ...fieldCommon,
1149
- strings: draftStrings,
1098
+ strings: shownStrings,
1150
1099
  value: null,
1151
- displayText: (0, _fieldText.formatMultipleText)(draftValues, type, dateFormat, timeFormat, draftDateText, draftStrings),
1100
+ displayText: (0, _fieldText.formatMultipleText)(draftValues, type, dateFormat, timeFormat, draftDateText, shownStrings),
1152
1101
  placeholder: sheetOptions?.fieldPlaceholder,
1153
1102
  allowClear: allowClear,
1154
1103
  onClear: clearInSheet,
@@ -1163,13 +1112,20 @@ const DateTimePicker = props => {
1163
1112
  dual: strings.calendarDual ?? _defaultStrings.DEFAULT_STRINGS.calendarDual
1164
1113
  };
1165
1114
  const systemA11yLabel = strings.a11yCalendarSystem ?? _defaultStrings.DEFAULT_STRINGS.a11yCalendarSystem;
1115
+ // A calendar or tab the user picks is an edit: 'instant' saves the selection
1116
+ // again with that calendar.
1166
1117
  const chooseSystem = next => {
1167
1118
  if (next === system) return;
1119
+ edited.current = true;
1168
1120
  setChosenSystem(next);
1169
1121
  if (next === 'dual') setDualTab(dualDefaultTab);
1170
1122
  echo.current = next;
1171
1123
  onCalendarSystemChange?.(next);
1172
1124
  };
1125
+ const chooseTab = next => {
1126
+ edited.current = true;
1127
+ setDualTab(next);
1128
+ };
1173
1129
  const calendarSwitchNode = !switchSystems ? null : renderCalendarSwitch ? renderCalendarSwitch({
1174
1130
  systems: switchSystems,
1175
1131
  selected: system,
@@ -1201,7 +1157,7 @@ const DateTimePicker = props => {
1201
1157
  label: systemLabels.hijri
1202
1158
  }],
1203
1159
  selected: dualTab,
1204
- onSelect: setDualTab,
1160
+ onSelect: chooseTab,
1205
1161
  accessibilityLabel: systemA11yLabel,
1206
1162
  tokens: tokens,
1207
1163
  styles: styles,
@@ -1217,16 +1173,16 @@ const DateTimePicker = props => {
1217
1173
  children: node
1218
1174
  })
1219
1175
  });
1220
- // Another calendar opens on the days the last one showed, else on the
1221
- // selection (shown or not), else where the picker would open.
1222
- const calendarAnchor = shownAnchor.current ?? (selectionShown ? null : (0, _selection.firstSelectedDay)(selection)) ?? initialVisibleDate;
1176
+ // Another calendar opens on the days the last one showed, else where the
1177
+ // picker would open.
1178
+ const calendarAnchor = shownAnchor.current ?? initialVisibleDate;
1223
1179
  const calendar = /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
1224
1180
  style: folded ? FOLDED : undefined,
1225
1181
  children: [calendarSwitchNode, dualTabs, mirrored(/*#__PURE__*/(0, _jsxRuntime.jsx)(_Calendar.default, {
1226
- constraints: selectionShown ? calendarConstraints : constraints,
1182
+ constraints: calendarConstraints,
1227
1183
  today: today,
1228
1184
  initialVisibleDate: calendarAnchor,
1229
- selection: selectionShown ? selection : emptySelection(selection),
1185
+ selection: selection,
1230
1186
  onSelectDay: onSelectDayStable,
1231
1187
  firstDayOfWeek: firstDayOfWeek,
1232
1188
  showOutsideDays: showOutsideDays,
@@ -1289,15 +1245,11 @@ const DateTimePicker = props => {
1289
1245
  // Shown while the selection is still the one it was given for.
1290
1246
  const validationMessage = validation && (validation.key === JSON.stringify(draft.toWire()) || validation.key === JSON.stringify((0, _draft.draftToWire)(preview, draftOptions))) ? validation.message : null;
1291
1247
 
1292
- // The field writes the date in the digits of the calendar it was picked in;
1293
- // the calendar and the time rows follow the tab that shows.
1248
+ // The field, the calendar and the time rows follow the tab that shows.
1294
1249
  const sheetContent = /*#__PURE__*/(0, _jsxRuntime.jsxs)(_jsxRuntime.Fragment, {
1295
- children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_numerals2.NumeralsContext.Provider, {
1296
- value: digitsFor(draftSystem),
1297
- children: sheetField
1298
- }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_numerals2.NumeralsContext.Provider, {
1250
+ children: [/*#__PURE__*/(0, _jsxRuntime.jsxs)(_numerals2.NumeralsContext.Provider, {
1299
1251
  value: digitsFor(shownSystem),
1300
- children: calendar
1252
+ children: [sheetField, calendar]
1301
1253
  }), timeRow == null ? null :
1302
1254
  /*#__PURE__*/
1303
1255
  // Measured for the scroll to the time (sheet.scrollToTime).