rn-grid-date-time-picker 1.0.0 → 1.2.0
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 +86 -0
- package/README.md +100 -638
- package/lib/commonjs/adapters/utcMidnight.js +15 -6
- package/lib/commonjs/adapters/utcMidnight.js.map +1 -1
- package/lib/commonjs/components/DateTimePicker.js +484 -85
- package/lib/commonjs/components/DateTimePicker.js.map +1 -1
- package/lib/commonjs/components/calendar/Calendar.js +21 -7
- package/lib/commonjs/components/calendar/Calendar.js.map +1 -1
- package/lib/commonjs/components/calendar/CalendarHeader.js +46 -6
- package/lib/commonjs/components/calendar/CalendarHeader.js.map +1 -1
- package/lib/commonjs/components/calendar/CalendarSwitch.js +150 -0
- package/lib/commonjs/components/calendar/CalendarSwitch.js.map +1 -0
- package/lib/commonjs/components/calendar/DayCell.js +8 -3
- package/lib/commonjs/components/calendar/DayCell.js.map +1 -1
- package/lib/commonjs/components/calendar/DayGrid.js +13 -2
- package/lib/commonjs/components/calendar/DayGrid.js.map +1 -1
- package/lib/commonjs/components/calendar/PeriodGrid.js +3 -1
- package/lib/commonjs/components/calendar/PeriodGrid.js.map +1 -1
- package/lib/commonjs/components/calendar/calendarStyles.js +3 -2
- package/lib/commonjs/components/calendar/calendarStyles.js.map +1 -1
- package/lib/commonjs/components/calendar/localeNames.js +46 -24
- package/lib/commonjs/components/calendar/localeNames.js.map +1 -1
- package/lib/commonjs/components/calendar/selection.js +9 -1
- package/lib/commonjs/components/calendar/selection.js.map +1 -1
- package/lib/commonjs/components/container/DefaultSheet.js +42 -11
- package/lib/commonjs/components/container/DefaultSheet.js.map +1 -1
- package/lib/commonjs/components/container/PickerContainer.js +4 -0
- package/lib/commonjs/components/container/PickerContainer.js.map +1 -1
- package/lib/commonjs/components/container/PickerLayout.js +1 -1
- package/lib/commonjs/components/container/PickerLayout.js.map +1 -1
- package/lib/commonjs/components/container/containerStyles.js +12 -4
- package/lib/commonjs/components/container/containerStyles.js.map +1 -1
- package/lib/commonjs/components/field/RangeFields.js +5 -1
- package/lib/commonjs/components/field/RangeFields.js.map +1 -1
- package/lib/commonjs/components/field/SegmentedInput.js +8 -3
- package/lib/commonjs/components/field/SegmentedInput.js.map +1 -1
- package/lib/commonjs/components/field/ValueField.js +64 -8
- package/lib/commonjs/components/field/ValueField.js.map +1 -1
- package/lib/commonjs/components/field/fieldStyles.js +17 -0
- package/lib/commonjs/components/field/fieldStyles.js.map +1 -1
- package/lib/commonjs/components/field/fieldText.js +29 -13
- package/lib/commonjs/components/field/fieldText.js.map +1 -1
- package/lib/commonjs/components/hooks/useBottomInset.js +32 -0
- package/lib/commonjs/components/hooks/useBottomInset.js.map +1 -0
- package/lib/commonjs/components/hooks/useKeyboardFold.js +36 -13
- package/lib/commonjs/components/hooks/useKeyboardFold.js.map +1 -1
- package/lib/commonjs/components/hooks/useOpenPicker.js +39 -17
- package/lib/commonjs/components/hooks/useOpenPicker.js.map +1 -1
- package/lib/commonjs/components/hooks/useTypedSegments.js +6 -2
- package/lib/commonjs/components/hooks/useTypedSegments.js.map +1 -1
- package/lib/commonjs/components/theme/defaultStrings.js +13 -3
- package/lib/commonjs/components/theme/defaultStrings.js.map +1 -1
- package/lib/commonjs/components/theme/direction.js +17 -0
- package/lib/commonjs/components/theme/direction.js.map +1 -0
- package/lib/commonjs/components/theme/numerals.js +16 -0
- package/lib/commonjs/components/theme/numerals.js.map +1 -0
- package/lib/commonjs/components/theme/styleKeys.js +10 -2
- package/lib/commonjs/components/theme/styleKeys.js.map +1 -1
- package/lib/commonjs/components/theme/tokens.js +4 -0
- package/lib/commonjs/components/theme/tokens.js.map +1 -1
- package/lib/commonjs/components/time/TimeRow.js +10 -2
- package/lib/commonjs/components/time/TimeRow.js.map +1 -1
- package/lib/commonjs/components/time/TypedTime.js +3 -1
- package/lib/commonjs/components/time/TypedTime.js.map +1 -1
- package/lib/commonjs/components/time/Wheel.js +20 -3
- package/lib/commonjs/components/time/Wheel.js.map +1 -1
- package/lib/commonjs/components/time/timeStyles.js +4 -5
- package/lib/commonjs/components/time/timeStyles.js.map +1 -1
- package/lib/commonjs/components/types.js +10 -0
- package/lib/commonjs/components/types.js.map +1 -1
- package/lib/commonjs/core/calendarSystem.js +123 -22
- package/lib/commonjs/core/calendarSystem.js.map +1 -1
- package/lib/commonjs/core/digits.js +25 -0
- package/lib/commonjs/core/digits.js.map +1 -0
- package/lib/commonjs/core/draft.js +31 -10
- package/lib/commonjs/core/draft.js.map +1 -1
- package/lib/commonjs/core/locale.js +20 -4
- package/lib/commonjs/core/locale.js.map +1 -1
- package/lib/commonjs/core/numerals.js +46 -0
- package/lib/commonjs/core/numerals.js.map +1 -0
- package/lib/commonjs/core/segmentEditor.js +9 -0
- package/lib/commonjs/core/segmentEditor.js.map +1 -1
- package/lib/commonjs/core/segments.js +2 -11
- package/lib/commonjs/core/segments.js.map +1 -1
- package/lib/commonjs/core/selectionRules.js +2 -1
- package/lib/commonjs/core/selectionRules.js.map +1 -1
- package/lib/commonjs/core/timeEntry.js +7 -91
- package/lib/commonjs/core/timeEntry.js.map +1 -1
- package/lib/commonjs/icons/index.js +5 -1
- package/lib/commonjs/icons/index.js.map +1 -1
- package/lib/commonjs/index.js +20 -0
- package/lib/commonjs/index.js.map +1 -1
- package/lib/module/adapters/utcMidnight.js +14 -5
- package/lib/module/adapters/utcMidnight.js.map +1 -1
- package/lib/module/components/DateTimePicker.js +490 -91
- package/lib/module/components/DateTimePicker.js.map +1 -1
- package/lib/module/components/calendar/Calendar.js +25 -11
- package/lib/module/components/calendar/Calendar.js.map +1 -1
- package/lib/module/components/calendar/CalendarHeader.js +47 -7
- package/lib/module/components/calendar/CalendarHeader.js.map +1 -1
- package/lib/module/components/calendar/CalendarSwitch.js +145 -0
- package/lib/module/components/calendar/CalendarSwitch.js.map +1 -0
- package/lib/module/components/calendar/DayCell.js +8 -3
- package/lib/module/components/calendar/DayCell.js.map +1 -1
- package/lib/module/components/calendar/DayGrid.js +14 -3
- package/lib/module/components/calendar/DayGrid.js.map +1 -1
- package/lib/module/components/calendar/PeriodGrid.js +3 -1
- package/lib/module/components/calendar/PeriodGrid.js.map +1 -1
- package/lib/module/components/calendar/calendarStyles.js +3 -2
- package/lib/module/components/calendar/calendarStyles.js.map +1 -1
- package/lib/module/components/calendar/localeNames.js +46 -24
- package/lib/module/components/calendar/localeNames.js.map +1 -1
- package/lib/module/components/calendar/selection.js +7 -0
- package/lib/module/components/calendar/selection.js.map +1 -1
- package/lib/module/components/container/DefaultSheet.js +42 -12
- package/lib/module/components/container/DefaultSheet.js.map +1 -1
- package/lib/module/components/container/PickerContainer.js +4 -0
- package/lib/module/components/container/PickerContainer.js.map +1 -1
- package/lib/module/components/container/PickerLayout.js +1 -1
- package/lib/module/components/container/PickerLayout.js.map +1 -1
- package/lib/module/components/container/containerStyles.js +11 -3
- package/lib/module/components/container/containerStyles.js.map +1 -1
- package/lib/module/components/field/RangeFields.js +5 -1
- package/lib/module/components/field/RangeFields.js.map +1 -1
- package/lib/module/components/field/SegmentedInput.js +8 -3
- package/lib/module/components/field/SegmentedInput.js.map +1 -1
- package/lib/module/components/field/ValueField.js +66 -10
- package/lib/module/components/field/ValueField.js.map +1 -1
- package/lib/module/components/field/fieldStyles.js +17 -0
- package/lib/module/components/field/fieldStyles.js.map +1 -1
- package/lib/module/components/field/fieldText.js +28 -11
- package/lib/module/components/field/fieldText.js.map +1 -1
- package/lib/module/components/hooks/useBottomInset.js +27 -0
- package/lib/module/components/hooks/useBottomInset.js.map +1 -0
- package/lib/module/components/hooks/useKeyboardFold.js +33 -12
- package/lib/module/components/hooks/useKeyboardFold.js.map +1 -1
- package/lib/module/components/hooks/useOpenPicker.js +39 -17
- package/lib/module/components/hooks/useOpenPicker.js.map +1 -1
- package/lib/module/components/hooks/useTypedSegments.js +6 -2
- package/lib/module/components/hooks/useTypedSegments.js.map +1 -1
- package/lib/module/components/theme/defaultStrings.js +11 -2
- package/lib/module/components/theme/defaultStrings.js.map +1 -1
- package/lib/module/components/theme/direction.js +13 -0
- package/lib/module/components/theme/direction.js.map +1 -0
- package/lib/module/components/theme/numerals.js +12 -0
- package/lib/module/components/theme/numerals.js.map +1 -0
- package/lib/module/components/theme/styleKeys.js +10 -2
- package/lib/module/components/theme/styleKeys.js.map +1 -1
- package/lib/module/components/theme/tokens.js +4 -0
- package/lib/module/components/theme/tokens.js.map +1 -1
- package/lib/module/components/time/TimeRow.js +11 -3
- package/lib/module/components/time/TimeRow.js.map +1 -1
- package/lib/module/components/time/TypedTime.js +3 -1
- package/lib/module/components/time/TypedTime.js.map +1 -1
- package/lib/module/components/time/Wheel.js +18 -2
- package/lib/module/components/time/Wheel.js.map +1 -1
- package/lib/module/components/time/timeStyles.js +4 -5
- package/lib/module/components/time/timeStyles.js.map +1 -1
- package/lib/module/components/types.js +10 -0
- package/lib/module/components/types.js.map +1 -1
- package/lib/module/core/calendarSystem.js +118 -19
- package/lib/module/core/calendarSystem.js.map +1 -1
- package/lib/module/core/digits.js +20 -0
- package/lib/module/core/digits.js.map +1 -0
- package/lib/module/core/draft.js +29 -9
- package/lib/module/core/draft.js.map +1 -1
- package/lib/module/core/locale.js +18 -3
- package/lib/module/core/locale.js.map +1 -1
- package/lib/module/core/numerals.js +39 -0
- package/lib/module/core/numerals.js.map +1 -0
- package/lib/module/core/segmentEditor.js +9 -0
- package/lib/module/core/segmentEditor.js.map +1 -1
- package/lib/module/core/segments.js +0 -8
- package/lib/module/core/segments.js.map +1 -1
- package/lib/module/core/selectionRules.js +2 -1
- package/lib/module/core/selectionRules.js.map +1 -1
- package/lib/module/core/timeEntry.js +7 -89
- package/lib/module/core/timeEntry.js.map +1 -1
- package/lib/module/icons/index.js +5 -1
- package/lib/module/icons/index.js.map +1 -1
- package/lib/module/index.js +2 -0
- package/lib/module/index.js.map +1 -1
- package/lib/typescript/commonjs/src/adapters/utcMidnight.d.ts.map +1 -1
- package/lib/typescript/commonjs/src/components/DateTimePicker.d.ts.map +1 -1
- package/lib/typescript/commonjs/src/components/calendar/Calendar.d.ts +8 -4
- package/lib/typescript/commonjs/src/components/calendar/Calendar.d.ts.map +1 -1
- package/lib/typescript/commonjs/src/components/calendar/CalendarHeader.d.ts.map +1 -1
- package/lib/typescript/commonjs/src/components/calendar/CalendarSwitch.d.ts +26 -0
- package/lib/typescript/commonjs/src/components/calendar/CalendarSwitch.d.ts.map +1 -0
- package/lib/typescript/commonjs/src/components/calendar/DayCell.d.ts +1 -1
- package/lib/typescript/commonjs/src/components/calendar/DayCell.d.ts.map +1 -1
- package/lib/typescript/commonjs/src/components/calendar/DayGrid.d.ts +2 -0
- package/lib/typescript/commonjs/src/components/calendar/DayGrid.d.ts.map +1 -1
- package/lib/typescript/commonjs/src/components/calendar/PeriodGrid.d.ts.map +1 -1
- package/lib/typescript/commonjs/src/components/calendar/calendarStyles.d.ts +1 -1
- package/lib/typescript/commonjs/src/components/calendar/calendarStyles.d.ts.map +1 -1
- package/lib/typescript/commonjs/src/components/calendar/localeNames.d.ts +8 -3
- package/lib/typescript/commonjs/src/components/calendar/localeNames.d.ts.map +1 -1
- package/lib/typescript/commonjs/src/components/calendar/selection.d.ts +2 -0
- package/lib/typescript/commonjs/src/components/calendar/selection.d.ts.map +1 -1
- package/lib/typescript/commonjs/src/components/container/DefaultSheet.d.ts +10 -2
- package/lib/typescript/commonjs/src/components/container/DefaultSheet.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/containerStyles.d.ts +6 -0
- package/lib/typescript/commonjs/src/components/container/containerStyles.d.ts.map +1 -1
- package/lib/typescript/commonjs/src/components/field/RangeFields.d.ts +5 -0
- package/lib/typescript/commonjs/src/components/field/RangeFields.d.ts.map +1 -1
- package/lib/typescript/commonjs/src/components/field/SegmentedInput.d.ts.map +1 -1
- package/lib/typescript/commonjs/src/components/field/ValueField.d.ts +7 -0
- package/lib/typescript/commonjs/src/components/field/ValueField.d.ts.map +1 -1
- package/lib/typescript/commonjs/src/components/field/fieldStyles.d.ts +15 -0
- package/lib/typescript/commonjs/src/components/field/fieldStyles.d.ts.map +1 -1
- package/lib/typescript/commonjs/src/components/field/fieldText.d.ts +14 -5
- package/lib/typescript/commonjs/src/components/field/fieldText.d.ts.map +1 -1
- package/lib/typescript/commonjs/src/components/hooks/useBottomInset.d.ts +8 -0
- package/lib/typescript/commonjs/src/components/hooks/useBottomInset.d.ts.map +1 -0
- package/lib/typescript/commonjs/src/components/hooks/useKeyboardFold.d.ts +10 -2
- package/lib/typescript/commonjs/src/components/hooks/useKeyboardFold.d.ts.map +1 -1
- package/lib/typescript/commonjs/src/components/hooks/useOpenPicker.d.ts +2 -1
- package/lib/typescript/commonjs/src/components/hooks/useOpenPicker.d.ts.map +1 -1
- package/lib/typescript/commonjs/src/components/hooks/useTypedSegments.d.ts +3 -2
- package/lib/typescript/commonjs/src/components/hooks/useTypedSegments.d.ts.map +1 -1
- package/lib/typescript/commonjs/src/components/theme/defaultStrings.d.ts +13 -2
- package/lib/typescript/commonjs/src/components/theme/defaultStrings.d.ts.map +1 -1
- package/lib/typescript/commonjs/src/components/theme/direction.d.ts +8 -0
- package/lib/typescript/commonjs/src/components/theme/direction.d.ts.map +1 -0
- package/lib/typescript/commonjs/src/components/theme/numerals.d.ts +7 -0
- package/lib/typescript/commonjs/src/components/theme/numerals.d.ts.map +1 -0
- package/lib/typescript/commonjs/src/components/theme/styleKeys.d.ts +1 -1
- package/lib/typescript/commonjs/src/components/theme/styleKeys.d.ts.map +1 -1
- package/lib/typescript/commonjs/src/components/theme/tokens.d.ts +3 -1
- package/lib/typescript/commonjs/src/components/theme/tokens.d.ts.map +1 -1
- package/lib/typescript/commonjs/src/components/time/TimeRow.d.ts.map +1 -1
- package/lib/typescript/commonjs/src/components/time/TypedTime.d.ts.map +1 -1
- package/lib/typescript/commonjs/src/components/time/Wheel.d.ts +5 -0
- package/lib/typescript/commonjs/src/components/time/Wheel.d.ts.map +1 -1
- package/lib/typescript/commonjs/src/components/time/timeStyles.d.ts +0 -2
- package/lib/typescript/commonjs/src/components/time/timeStyles.d.ts.map +1 -1
- package/lib/typescript/commonjs/src/components/types.d.ts +84 -4
- package/lib/typescript/commonjs/src/components/types.d.ts.map +1 -1
- package/lib/typescript/commonjs/src/core/calendarSystem.d.ts +20 -5
- package/lib/typescript/commonjs/src/core/calendarSystem.d.ts.map +1 -1
- package/lib/typescript/commonjs/src/core/digits.d.ts +3 -0
- package/lib/typescript/commonjs/src/core/digits.d.ts.map +1 -0
- package/lib/typescript/commonjs/src/core/draft.d.ts +12 -1
- package/lib/typescript/commonjs/src/core/draft.d.ts.map +1 -1
- package/lib/typescript/commonjs/src/core/locale.d.ts +6 -0
- package/lib/typescript/commonjs/src/core/locale.d.ts.map +1 -1
- package/lib/typescript/commonjs/src/core/numerals.d.ts +15 -0
- package/lib/typescript/commonjs/src/core/numerals.d.ts.map +1 -0
- package/lib/typescript/commonjs/src/core/segmentEditor.d.ts.map +1 -1
- package/lib/typescript/commonjs/src/core/segments.d.ts +0 -2
- package/lib/typescript/commonjs/src/core/segments.d.ts.map +1 -1
- package/lib/typescript/commonjs/src/core/selectionRules.d.ts +3 -2
- package/lib/typescript/commonjs/src/core/selectionRules.d.ts.map +1 -1
- package/lib/typescript/commonjs/src/core/timeEntry.d.ts +4 -24
- package/lib/typescript/commonjs/src/core/timeEntry.d.ts.map +1 -1
- package/lib/typescript/commonjs/src/icons/index.d.ts.map +1 -1
- package/lib/typescript/commonjs/src/index.d.ts +7 -3
- package/lib/typescript/commonjs/src/index.d.ts.map +1 -1
- package/lib/typescript/module/src/adapters/utcMidnight.d.ts.map +1 -1
- package/lib/typescript/module/src/components/DateTimePicker.d.ts.map +1 -1
- package/lib/typescript/module/src/components/calendar/Calendar.d.ts +8 -4
- package/lib/typescript/module/src/components/calendar/Calendar.d.ts.map +1 -1
- package/lib/typescript/module/src/components/calendar/CalendarHeader.d.ts.map +1 -1
- package/lib/typescript/module/src/components/calendar/CalendarSwitch.d.ts +26 -0
- package/lib/typescript/module/src/components/calendar/CalendarSwitch.d.ts.map +1 -0
- package/lib/typescript/module/src/components/calendar/DayCell.d.ts +1 -1
- package/lib/typescript/module/src/components/calendar/DayCell.d.ts.map +1 -1
- package/lib/typescript/module/src/components/calendar/DayGrid.d.ts +2 -0
- package/lib/typescript/module/src/components/calendar/DayGrid.d.ts.map +1 -1
- package/lib/typescript/module/src/components/calendar/PeriodGrid.d.ts.map +1 -1
- package/lib/typescript/module/src/components/calendar/calendarStyles.d.ts +1 -1
- package/lib/typescript/module/src/components/calendar/calendarStyles.d.ts.map +1 -1
- package/lib/typescript/module/src/components/calendar/localeNames.d.ts +8 -3
- package/lib/typescript/module/src/components/calendar/localeNames.d.ts.map +1 -1
- package/lib/typescript/module/src/components/calendar/selection.d.ts +2 -0
- package/lib/typescript/module/src/components/calendar/selection.d.ts.map +1 -1
- package/lib/typescript/module/src/components/container/DefaultSheet.d.ts +10 -2
- package/lib/typescript/module/src/components/container/DefaultSheet.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/containerStyles.d.ts +6 -0
- package/lib/typescript/module/src/components/container/containerStyles.d.ts.map +1 -1
- package/lib/typescript/module/src/components/field/RangeFields.d.ts +5 -0
- package/lib/typescript/module/src/components/field/RangeFields.d.ts.map +1 -1
- package/lib/typescript/module/src/components/field/SegmentedInput.d.ts.map +1 -1
- package/lib/typescript/module/src/components/field/ValueField.d.ts +7 -0
- package/lib/typescript/module/src/components/field/ValueField.d.ts.map +1 -1
- package/lib/typescript/module/src/components/field/fieldStyles.d.ts +15 -0
- package/lib/typescript/module/src/components/field/fieldStyles.d.ts.map +1 -1
- package/lib/typescript/module/src/components/field/fieldText.d.ts +14 -5
- package/lib/typescript/module/src/components/field/fieldText.d.ts.map +1 -1
- package/lib/typescript/module/src/components/hooks/useBottomInset.d.ts +8 -0
- package/lib/typescript/module/src/components/hooks/useBottomInset.d.ts.map +1 -0
- package/lib/typescript/module/src/components/hooks/useKeyboardFold.d.ts +10 -2
- package/lib/typescript/module/src/components/hooks/useKeyboardFold.d.ts.map +1 -1
- package/lib/typescript/module/src/components/hooks/useOpenPicker.d.ts +2 -1
- package/lib/typescript/module/src/components/hooks/useOpenPicker.d.ts.map +1 -1
- package/lib/typescript/module/src/components/hooks/useTypedSegments.d.ts +3 -2
- package/lib/typescript/module/src/components/hooks/useTypedSegments.d.ts.map +1 -1
- package/lib/typescript/module/src/components/theme/defaultStrings.d.ts +13 -2
- package/lib/typescript/module/src/components/theme/defaultStrings.d.ts.map +1 -1
- package/lib/typescript/module/src/components/theme/direction.d.ts +8 -0
- package/lib/typescript/module/src/components/theme/direction.d.ts.map +1 -0
- package/lib/typescript/module/src/components/theme/numerals.d.ts +7 -0
- package/lib/typescript/module/src/components/theme/numerals.d.ts.map +1 -0
- package/lib/typescript/module/src/components/theme/styleKeys.d.ts +1 -1
- package/lib/typescript/module/src/components/theme/styleKeys.d.ts.map +1 -1
- package/lib/typescript/module/src/components/theme/tokens.d.ts +3 -1
- package/lib/typescript/module/src/components/theme/tokens.d.ts.map +1 -1
- package/lib/typescript/module/src/components/time/TimeRow.d.ts.map +1 -1
- package/lib/typescript/module/src/components/time/TypedTime.d.ts.map +1 -1
- package/lib/typescript/module/src/components/time/Wheel.d.ts +5 -0
- package/lib/typescript/module/src/components/time/Wheel.d.ts.map +1 -1
- package/lib/typescript/module/src/components/time/timeStyles.d.ts +0 -2
- package/lib/typescript/module/src/components/time/timeStyles.d.ts.map +1 -1
- package/lib/typescript/module/src/components/types.d.ts +84 -4
- package/lib/typescript/module/src/components/types.d.ts.map +1 -1
- package/lib/typescript/module/src/core/calendarSystem.d.ts +20 -5
- package/lib/typescript/module/src/core/calendarSystem.d.ts.map +1 -1
- package/lib/typescript/module/src/core/digits.d.ts +3 -0
- package/lib/typescript/module/src/core/digits.d.ts.map +1 -0
- package/lib/typescript/module/src/core/draft.d.ts +12 -1
- package/lib/typescript/module/src/core/draft.d.ts.map +1 -1
- package/lib/typescript/module/src/core/locale.d.ts +6 -0
- package/lib/typescript/module/src/core/locale.d.ts.map +1 -1
- package/lib/typescript/module/src/core/numerals.d.ts +15 -0
- package/lib/typescript/module/src/core/numerals.d.ts.map +1 -0
- package/lib/typescript/module/src/core/segmentEditor.d.ts.map +1 -1
- package/lib/typescript/module/src/core/segments.d.ts +0 -2
- package/lib/typescript/module/src/core/segments.d.ts.map +1 -1
- package/lib/typescript/module/src/core/selectionRules.d.ts +3 -2
- package/lib/typescript/module/src/core/selectionRules.d.ts.map +1 -1
- package/lib/typescript/module/src/core/timeEntry.d.ts +4 -24
- package/lib/typescript/module/src/core/timeEntry.d.ts.map +1 -1
- package/lib/typescript/module/src/icons/index.d.ts.map +1 -1
- package/lib/typescript/module/src/index.d.ts +7 -3
- package/lib/typescript/module/src/index.d.ts.map +1 -1
- package/package.json +22 -7
- package/src/adapters/utcMidnight.ts +14 -5
- package/src/components/DateTimePicker.tsx +607 -108
- package/src/components/calendar/Calendar.tsx +54 -16
- package/src/components/calendar/CalendarHeader.tsx +41 -9
- package/src/components/calendar/CalendarSwitch.tsx +170 -0
- package/src/components/calendar/DayCell.tsx +16 -4
- package/src/components/calendar/DayGrid.tsx +18 -3
- package/src/components/calendar/PeriodGrid.tsx +3 -1
- package/src/components/calendar/calendarStyles.ts +3 -2
- package/src/components/calendar/localeNames.ts +52 -22
- package/src/components/calendar/selection.ts +8 -0
- package/src/components/container/DefaultSheet.tsx +60 -14
- package/src/components/container/PickerContainer.tsx +4 -0
- package/src/components/container/PickerLayout.tsx +1 -1
- package/src/components/container/containerStyles.ts +12 -3
- package/src/components/field/RangeFields.tsx +12 -2
- package/src/components/field/SegmentedInput.tsx +8 -3
- package/src/components/field/ValueField.tsx +94 -18
- package/src/components/field/fieldStyles.ts +6 -0
- package/src/components/field/fieldText.ts +68 -15
- package/src/components/hooks/useBottomInset.ts +33 -0
- package/src/components/hooks/useKeyboardFold.ts +41 -13
- package/src/components/hooks/useOpenPicker.ts +41 -18
- package/src/components/hooks/useTypedSegments.ts +7 -3
- package/src/components/theme/defaultStrings.ts +29 -4
- package/src/components/theme/direction.ts +12 -0
- package/src/components/theme/numerals.ts +11 -0
- package/src/components/theme/styleKeys.ts +18 -1
- package/src/components/theme/tokens.ts +5 -1
- package/src/components/time/TimeRow.tsx +23 -3
- package/src/components/time/TypedTime.tsx +3 -1
- package/src/components/time/Wheel.tsx +19 -5
- package/src/components/time/timeStyles.ts +2 -3
- package/src/components/types.ts +86 -4
- package/src/core/calendarSystem.ts +145 -21
- package/src/core/digits.ts +21 -0
- package/src/core/draft.ts +41 -13
- package/src/core/locale.ts +20 -3
- package/src/core/numerals.ts +35 -0
- package/src/core/segmentEditor.ts +13 -0
- package/src/core/segments.ts +0 -8
- package/src/core/selectionRules.ts +3 -2
- package/src/core/timeEntry.ts +6 -96
- package/src/icons/index.tsx +3 -1
- package/src/index.tsx +12 -2
|
@@ -6,20 +6,33 @@ import React, {
|
|
|
6
6
|
useState,
|
|
7
7
|
} from 'react';
|
|
8
8
|
import { DateTime } from 'luxon';
|
|
9
|
+
import { View } from 'react-native';
|
|
9
10
|
import { resolveAdapter } from '../adapters';
|
|
10
|
-
import {
|
|
11
|
+
import {
|
|
12
|
+
compareCalendarDates,
|
|
13
|
+
daysBetween,
|
|
14
|
+
isSameDay,
|
|
15
|
+
} from '../core/calendarDate';
|
|
11
16
|
import { createConstraints } from '../core/constraints';
|
|
12
17
|
import {
|
|
18
|
+
clearDraft as emptied,
|
|
13
19
|
createDraft,
|
|
14
20
|
DEFAULT_TIME,
|
|
15
21
|
draftToWire,
|
|
22
|
+
sameDayRangeOk,
|
|
16
23
|
setTime as applyTime,
|
|
17
24
|
validateDraft,
|
|
18
25
|
type Draft,
|
|
19
26
|
type DraftOptions,
|
|
20
27
|
type DraftTarget,
|
|
21
28
|
} from '../core/draft';
|
|
22
|
-
import {
|
|
29
|
+
import {
|
|
30
|
+
hijriDateText,
|
|
31
|
+
hijriPeriods,
|
|
32
|
+
isHijriSupported,
|
|
33
|
+
resolveCalendarSystem,
|
|
34
|
+
type CalendarSystem,
|
|
35
|
+
} from '../core/calendarSystem';
|
|
23
36
|
import {
|
|
24
37
|
defaultLocale,
|
|
25
38
|
deriveDateFormat,
|
|
@@ -27,6 +40,7 @@ import {
|
|
|
27
40
|
deriveTimeFormat,
|
|
28
41
|
normalizeDateFormat,
|
|
29
42
|
} from '../core/locale';
|
|
43
|
+
import { arabicDigits, latinDigits, localeDigits } from '../core/numerals';
|
|
30
44
|
import { withSelectionRules } from '../core/selectionRules';
|
|
31
45
|
import type { ParsedSegments } from '../core/segments';
|
|
32
46
|
import { checkTime, type TimeEntryIssue } from '../core/timeEntry';
|
|
@@ -38,13 +52,18 @@ import type {
|
|
|
38
52
|
ValueAdapter,
|
|
39
53
|
} from '../core/types';
|
|
40
54
|
import Calendar from './calendar/Calendar';
|
|
55
|
+
import CalendarSwitch from './calendar/CalendarSwitch';
|
|
41
56
|
import { localeNames } from './calendar/localeNames';
|
|
42
|
-
import
|
|
57
|
+
import { RtlContext } from './theme/direction';
|
|
58
|
+
import { NumeralsContext } from './theme/numerals';
|
|
59
|
+
import { firstSelectedDay, type CalendarSelection } from './calendar/selection';
|
|
43
60
|
import PickerContainer from './container/PickerContainer';
|
|
44
61
|
import {
|
|
62
|
+
formatDateText,
|
|
45
63
|
formatMultipleText,
|
|
46
64
|
formatRangeText,
|
|
47
65
|
formatValueText,
|
|
66
|
+
type DateTextFormatter,
|
|
48
67
|
} from './field/fieldText';
|
|
49
68
|
import FieldMessages from './field/FieldMessages';
|
|
50
69
|
import RangeFields, { type RangeEnd } from './field/RangeFields';
|
|
@@ -54,7 +73,11 @@ import { useOpenPicker } from './hooks/useOpenPicker';
|
|
|
54
73
|
import { usePickerDraft } from './hooks/usePickerDraft';
|
|
55
74
|
import { useTodayTick } from './hooks/useTodayTick';
|
|
56
75
|
import TimeRows, { type PendingTimes } from './time/TimeRows';
|
|
57
|
-
import {
|
|
76
|
+
import {
|
|
77
|
+
DEFAULT_STRINGS,
|
|
78
|
+
compactStrings,
|
|
79
|
+
resolveStrings,
|
|
80
|
+
} from './theme/defaultStrings';
|
|
58
81
|
import { buildTokens } from './theme/tokens';
|
|
59
82
|
import {
|
|
60
83
|
DEFAULT_YEAR_RANGE,
|
|
@@ -68,9 +91,29 @@ const sameValue = (a: DateTimeValue, b: DateTimeValue): boolean =>
|
|
|
68
91
|
(a.time?.hour ?? -1) === (b.time?.hour ?? -1) &&
|
|
69
92
|
(a.time?.minute ?? -1) === (b.time?.minute ?? -1);
|
|
70
93
|
|
|
94
|
+
const ALL_SYSTEMS: CalendarSystem[] = ['gregory', 'hijri', 'dual'];
|
|
95
|
+
const RTL = { direction: 'rtl' } as const;
|
|
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
|
+
|
|
71
105
|
/** Minute steps that divide the hour. */
|
|
72
106
|
const MINUTE_STEPS = [1, 2, 3, 4, 5, 6, 10, 12, 15, 20, 30, 60];
|
|
73
107
|
|
|
108
|
+
/** A folded calendar stays mounted, so it comes back on the month and view it showed. */
|
|
109
|
+
const FOLDED = { display: 'none' } as const;
|
|
110
|
+
|
|
111
|
+
/** The later / earlier of a limit and a bound; the bound when there's no limit. */
|
|
112
|
+
const later = (a: CalendarDate | undefined, b: CalendarDate): CalendarDate =>
|
|
113
|
+
a && compareCalendarDates(a, b) > 0 ? a : b;
|
|
114
|
+
const earlier = (a: CalendarDate | undefined, b: CalendarDate): CalendarDate =>
|
|
115
|
+
a && compareCalendarDates(a, b) < 0 ? a : b;
|
|
116
|
+
|
|
74
117
|
/**
|
|
75
118
|
* A date & time picker: a value field that opens a sheet (or an inline panel)
|
|
76
119
|
* with a calendar and, for date-time values, a time row. Single, range and
|
|
@@ -110,6 +153,7 @@ const DateTimePicker: React.FC<DateTimePickerProps> = (props) => {
|
|
|
110
153
|
disabled = false,
|
|
111
154
|
readOnly = false,
|
|
112
155
|
allowTyping,
|
|
156
|
+
hideCalendarOnKeyboard = true,
|
|
113
157
|
allowFieldTyping,
|
|
114
158
|
allowClear,
|
|
115
159
|
errorMessage,
|
|
@@ -141,6 +185,11 @@ const DateTimePicker: React.FC<DateTimePickerProps> = (props) => {
|
|
|
141
185
|
initialVisibleDate,
|
|
142
186
|
enableMonthSwipe = true,
|
|
143
187
|
calendarSystem,
|
|
188
|
+
calendarSwitch,
|
|
189
|
+
onCalendarSystemChange,
|
|
190
|
+
dualDefaultTab = 'gregory',
|
|
191
|
+
hijriLocale = 'ar-SA',
|
|
192
|
+
renderCalendarSwitch,
|
|
144
193
|
showToday = false,
|
|
145
194
|
showHolidays = true,
|
|
146
195
|
onVisibleMonthChange,
|
|
@@ -148,6 +197,7 @@ const DateTimePicker: React.FC<DateTimePickerProps> = (props) => {
|
|
|
148
197
|
validate,
|
|
149
198
|
yearRange: yearRangeProp = DEFAULT_YEAR_RANGE,
|
|
150
199
|
locale,
|
|
200
|
+
numerals: numeralsProp,
|
|
151
201
|
timeZone,
|
|
152
202
|
now,
|
|
153
203
|
timeFormat: timeFormatProp,
|
|
@@ -167,6 +217,7 @@ const DateTimePicker: React.FC<DateTimePickerProps> = (props) => {
|
|
|
167
217
|
theme,
|
|
168
218
|
styles,
|
|
169
219
|
strings: stringOverrides,
|
|
220
|
+
hijriStrings: hijriOverrides,
|
|
170
221
|
testID,
|
|
171
222
|
} = props;
|
|
172
223
|
|
|
@@ -184,6 +235,87 @@ const DateTimePicker: React.FC<DateTimePickerProps> = (props) => {
|
|
|
184
235
|
// Month and year picks are for single dates only.
|
|
185
236
|
const precision =
|
|
186
237
|
type === 'date' && mode === 'single' ? precisionProp : 'day';
|
|
238
|
+
// The calendars the switch offers: none for month and year picks (always
|
|
239
|
+
// Gregorian), or when the platform has no Hijri calendar.
|
|
240
|
+
const switchKey = Array.isArray(calendarSwitch)
|
|
241
|
+
? calendarSwitch.join(',')
|
|
242
|
+
: String(!!calendarSwitch);
|
|
243
|
+
const switchSystems = useMemo((): CalendarSystem[] | null => {
|
|
244
|
+
if (!calendarSwitch || precision !== 'day' || !isHijriSupported())
|
|
245
|
+
return null;
|
|
246
|
+
const list =
|
|
247
|
+
calendarSwitch === true
|
|
248
|
+
? ALL_SYSTEMS
|
|
249
|
+
: ALL_SYSTEMS.filter((x) => calendarSwitch.includes(x)).sort(
|
|
250
|
+
(a, b) => calendarSwitch.indexOf(a) - calendarSwitch.indexOf(b)
|
|
251
|
+
);
|
|
252
|
+
return list.length > 1 ? list : null;
|
|
253
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps -- the list is compared by content
|
|
254
|
+
}, [switchKey, precision]);
|
|
255
|
+
const startSystem = (): CalendarSystem => {
|
|
256
|
+
const wanted = calendarSystem ?? 'gregory';
|
|
257
|
+
return switchSystems && !switchSystems.includes(wanted)
|
|
258
|
+
? (switchSystems[0] ?? wanted)
|
|
259
|
+
: wanted;
|
|
260
|
+
};
|
|
261
|
+
// The switch changes this; a new calendarSystem prop starts over from it.
|
|
262
|
+
const [chosenSystem, setChosenSystem] = useState(startSystem);
|
|
263
|
+
// The calendar the props open on, which a value from outside counts as
|
|
264
|
+
// picked in. The switch's own choice coming back as the calendarSystem prop
|
|
265
|
+
// (onCalendarSystemChange={setSystem}) isn't a new one.
|
|
266
|
+
const [openedOn, setOpenedOn] = useState(startSystem);
|
|
267
|
+
const echo = useRef<CalendarSystem | null>(null);
|
|
268
|
+
useEffect(
|
|
269
|
+
() => {
|
|
270
|
+
const next = startSystem();
|
|
271
|
+
const own = echo.current === next;
|
|
272
|
+
echo.current = null;
|
|
273
|
+
if (own) return;
|
|
274
|
+
setChosenSystem(next);
|
|
275
|
+
setOpenedOn(next);
|
|
276
|
+
},
|
|
277
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps -- only when the props change
|
|
278
|
+
[calendarSystem, switchKey]
|
|
279
|
+
);
|
|
280
|
+
// Month and year picks are Gregorian whatever the calendar system.
|
|
281
|
+
const system =
|
|
282
|
+
precision === 'day' ? resolveCalendarSystem(chosenSystem) : 'gregory';
|
|
283
|
+
// A dual calendar shows one of its two tabs; each open starts on dualDefaultTab.
|
|
284
|
+
const [dualTab, setDualTab] = useState(dualDefaultTab);
|
|
285
|
+
/** The calendar on screen. */
|
|
286
|
+
const shownSystem = system === 'dual' ? dualTab : system;
|
|
287
|
+
// A value from outside counts as picked in the calendar the props open on,
|
|
288
|
+
// not the one the user has since chosen in the switch.
|
|
289
|
+
const propSystem =
|
|
290
|
+
precision === 'day' ? resolveCalendarSystem(openedOn) : 'gregory';
|
|
291
|
+
const outsideSystem = propSystem === 'dual' ? dualDefaultTab : propSystem;
|
|
292
|
+
// Picks say which calendar they were made in only when one can be Hijri.
|
|
293
|
+
const hijriInPlay = switchSystems !== null || system !== 'gregory';
|
|
294
|
+
/**
|
|
295
|
+
* The digits for one calendar: a Hijri calendar (and a date picked in it)
|
|
296
|
+
* shows Arabic digits (١٢٣) in any language; a Gregorian one shows 0–9.
|
|
297
|
+
* `numerals` sets both.
|
|
298
|
+
*/
|
|
299
|
+
const digitsFor = (sys: 'gregory' | 'hijri') => {
|
|
300
|
+
const kind = numeralsProp ?? (sys === 'hijri' ? 'arabic' : 'latin');
|
|
301
|
+
if (kind === 'arabic') return arabicDigits;
|
|
302
|
+
if (kind === 'latin') return latinDigits;
|
|
303
|
+
return localeDigits(sys === 'hijri' ? hijriLocale : loc);
|
|
304
|
+
};
|
|
305
|
+
/**
|
|
306
|
+
* The strings for one calendar. A Hijri one takes `hijriStrings` first, then
|
|
307
|
+
* AM / PM in `hijriLocale` (ص / م), then `strings`.
|
|
308
|
+
*/
|
|
309
|
+
const hijriStrings = useMemo(
|
|
310
|
+
() => ({
|
|
311
|
+
...strings,
|
|
312
|
+
...hijriPeriods(hijriLocale),
|
|
313
|
+
...compactStrings(hijriOverrides),
|
|
314
|
+
}),
|
|
315
|
+
[strings, hijriLocale, hijriOverrides]
|
|
316
|
+
);
|
|
317
|
+
const stringsFor = (sys: 'gregory' | 'hijri') =>
|
|
318
|
+
sys === 'hijri' ? hijriStrings : strings;
|
|
187
319
|
const monthNames = useMemo(() => localeNames(loc).monthsLong, [loc]);
|
|
188
320
|
/** A month or year pick shown as text: "March 2026" or "2026". */
|
|
189
321
|
const periodText = (v: DateTimeValue | null): string => {
|
|
@@ -272,7 +404,7 @@ const DateTimePicker: React.FC<DateTimePickerProps> = (props) => {
|
|
|
272
404
|
const {
|
|
273
405
|
isOpen: sheetOpen,
|
|
274
406
|
open,
|
|
275
|
-
close,
|
|
407
|
+
close: closePicker,
|
|
276
408
|
} = useOpenPicker({
|
|
277
409
|
disabled,
|
|
278
410
|
readOnly,
|
|
@@ -282,7 +414,7 @@ const DateTimePicker: React.FC<DateTimePickerProps> = (props) => {
|
|
|
282
414
|
onClose,
|
|
283
415
|
});
|
|
284
416
|
const isOpen = isInline || sheetOpen;
|
|
285
|
-
const keyboardUp = useKeyboardFold(isOpen);
|
|
417
|
+
const keyboardUp = useKeyboardFold(isOpen && hideCalendarOnKeyboard);
|
|
286
418
|
// The picker's own boxes that have focus. Inline, another field on the
|
|
287
419
|
// screen can raise the keyboard, so the calendar folds only for these.
|
|
288
420
|
const [focusedParts, setFocusedParts] = useState<Record<string, boolean>>({});
|
|
@@ -349,6 +481,17 @@ const DateTimePicker: React.FC<DateTimePickerProps> = (props) => {
|
|
|
349
481
|
},
|
|
350
482
|
[loadHolidays]
|
|
351
483
|
);
|
|
484
|
+
// A month's grid also shows days of the months either side, and a Hijri
|
|
485
|
+
// month can run from December into January, so a month at either end of a
|
|
486
|
+
// year loads the year next to it too.
|
|
487
|
+
const loadHolidaysAround = useCallback(
|
|
488
|
+
(year: number, month: number) => {
|
|
489
|
+
loadHolidaysFor(year);
|
|
490
|
+
if (month >= 11 && year < yearRange.last) loadHolidaysFor(year + 1);
|
|
491
|
+
if (month === 1 && year > yearRange.first) loadHolidaysFor(year - 1);
|
|
492
|
+
},
|
|
493
|
+
[loadHolidaysFor, yearRange.first, yearRange.last]
|
|
494
|
+
);
|
|
352
495
|
const holidays = useMemo(
|
|
353
496
|
() => [...(holidaysProp ?? []), ...Object.values(loadedHolidays).flat()],
|
|
354
497
|
[holidaysProp, loadedHolidays]
|
|
@@ -357,11 +500,15 @@ const DateTimePicker: React.FC<DateTimePickerProps> = (props) => {
|
|
|
357
500
|
// Limits are worked out when the picker opens (the screen's own validation
|
|
358
501
|
// after Confirm stays the final check).
|
|
359
502
|
const weekdaysKey = disabledWeekdays?.join(',');
|
|
503
|
+
// Limits built with `dependsOnNow` ("from now on") are rebuilt every minute
|
|
504
|
+
// while the picker is open.
|
|
505
|
+
const [minuteTick, setMinuteTick] = useState(0);
|
|
360
506
|
const constraints = useMemo(() => {
|
|
361
507
|
const nowDate = now?.() ?? new Date();
|
|
508
|
+
// yearRange bounds the limits too, so days outside it are greyed.
|
|
362
509
|
const limits = {
|
|
363
|
-
minDate,
|
|
364
|
-
maxDate,
|
|
510
|
+
minDate: later(minDate, { year: yearRange.first, month: 1, day: 1 }),
|
|
511
|
+
maxDate: earlier(maxDate, { year: yearRange.last, month: 12, day: 31 }),
|
|
365
512
|
disabledDates,
|
|
366
513
|
enabledDates,
|
|
367
514
|
timeBounds,
|
|
@@ -383,8 +530,8 @@ const DateTimePicker: React.FC<DateTimePickerProps> = (props) => {
|
|
|
383
530
|
type,
|
|
384
531
|
timeZone: zone,
|
|
385
532
|
now: nowDate,
|
|
386
|
-
minDate,
|
|
387
|
-
maxDate,
|
|
533
|
+
minDate: limits.minDate,
|
|
534
|
+
maxDate: limits.maxDate,
|
|
388
535
|
isDisabled: disabledDates,
|
|
389
536
|
isEnabled: enabledDates,
|
|
390
537
|
timeBounds,
|
|
@@ -392,10 +539,11 @@ const DateTimePicker: React.FC<DateTimePickerProps> = (props) => {
|
|
|
392
539
|
holidays,
|
|
393
540
|
disableHolidays,
|
|
394
541
|
});
|
|
395
|
-
// eslint-disable-next-line react-hooks/exhaustive-deps -- `isOpen` and `
|
|
542
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps -- `isOpen`, `today` and `minuteTick` refresh the limits on open, at midnight and each minute; weekdays are compared by content
|
|
396
543
|
}, [
|
|
397
544
|
isOpen,
|
|
398
545
|
today,
|
|
546
|
+
minuteTick,
|
|
399
547
|
type,
|
|
400
548
|
zone,
|
|
401
549
|
now,
|
|
@@ -408,8 +556,27 @@ const DateTimePicker: React.FC<DateTimePickerProps> = (props) => {
|
|
|
408
556
|
weekdaysKey,
|
|
409
557
|
disableHolidays,
|
|
410
558
|
getConstraints,
|
|
559
|
+
yearRange,
|
|
411
560
|
]);
|
|
412
561
|
|
|
562
|
+
const followsClock = isOpen && constraints.dependsOnNow;
|
|
563
|
+
useEffect(() => {
|
|
564
|
+
if (!followsClock) return;
|
|
565
|
+
let timer: ReturnType<typeof setTimeout> | undefined;
|
|
566
|
+
const arm = () => {
|
|
567
|
+
const ms = 60_000 - ((now?.() ?? new Date()).getTime() % 60_000);
|
|
568
|
+
timer = setTimeout(() => {
|
|
569
|
+
setMinuteTick((n) => n + 1);
|
|
570
|
+
arm();
|
|
571
|
+
}, ms);
|
|
572
|
+
// Under Node (e.g. Jest) the timer must not keep the process alive.
|
|
573
|
+
(timer as { unref?: () => void }).unref?.();
|
|
574
|
+
};
|
|
575
|
+
arm();
|
|
576
|
+
return () => clearTimeout(timer);
|
|
577
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps -- armed while open with a clock-bound limit
|
|
578
|
+
}, [followsClock]);
|
|
579
|
+
|
|
413
580
|
// A typed field that doesn't parse blocks Confirm, per field: one end of a
|
|
414
581
|
// range can't hide the other end's problem.
|
|
415
582
|
const [typingBlocks, setTypingBlocks] = useState<
|
|
@@ -444,14 +611,30 @@ const DateTimePicker: React.FC<DateTimePickerProps> = (props) => {
|
|
|
444
611
|
const [wheelSpinning, setWheelSpinning] = useState(false);
|
|
445
612
|
// From/To layout: the end that calendar taps fill. perDate: the day the time row edits.
|
|
446
613
|
const [activeEnd, setActiveEnd] = useState<RangeEnd>('from');
|
|
614
|
+
// From/To layout: a From or To field was tapped, so the next day tap edits that end only.
|
|
615
|
+
const [endPicked, setEndPicked] = useState(false);
|
|
616
|
+
const activateEnd = useCallback((end: RangeEnd) => {
|
|
617
|
+
setActiveEnd(end);
|
|
618
|
+
setEndPicked(true);
|
|
619
|
+
}, []);
|
|
447
620
|
const [activeDay, setActiveDay] = useState<CalendarDate | null>(null);
|
|
448
621
|
const resetSelectionUi = useCallback(() => {
|
|
622
|
+
// What was typed is gone with the selection, so nothing blocks Confirm.
|
|
623
|
+
setTypingBlocks({});
|
|
449
624
|
setPendingTimes({});
|
|
450
625
|
setActiveEnd('from');
|
|
626
|
+
setEndPicked(false);
|
|
451
627
|
setActiveDay(null);
|
|
452
628
|
}, []);
|
|
629
|
+
// A day in the month the calendar last showed, so a switch to the other
|
|
630
|
+
// calendar opens on the same days. Each open starts from the value again.
|
|
631
|
+
const shownAnchor = useRef<CalendarDate | null>(null);
|
|
632
|
+
const rememberAnchor = useCallback((day: CalendarDate) => {
|
|
633
|
+
shownAnchor.current = day;
|
|
634
|
+
}, []);
|
|
453
635
|
// Opening or closing starts fresh: nothing edited, nothing blocked.
|
|
454
636
|
useEffect(() => {
|
|
637
|
+
shownAnchor.current = null;
|
|
455
638
|
edited.current = false;
|
|
456
639
|
setTypingBlocks({});
|
|
457
640
|
setTimeCheck(0);
|
|
@@ -529,6 +712,63 @@ const DateTimePicker: React.FC<DateTimePickerProps> = (props) => {
|
|
|
529
712
|
latest.current = { validate, onError, value };
|
|
530
713
|
const valueKey = JSON.stringify(value ?? '');
|
|
531
714
|
|
|
715
|
+
// A selection belongs to the calendar it was picked in (Gregorian or Hijri):
|
|
716
|
+
// it shows only there, and the field writes it in that calendar. A saved
|
|
717
|
+
// value remembers its calendar; one from outside counts as picked in the
|
|
718
|
+
// calendar the picker opens on.
|
|
719
|
+
// The saved value as a draft, for the on-screen field and for comparing
|
|
720
|
+
// values by what they hold.
|
|
721
|
+
const saved = useMemo(
|
|
722
|
+
() => createDraft(mode, value, draftOptions),
|
|
723
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps -- the value is compared by content (`valueKey`)
|
|
724
|
+
[valueKey, mode, draftOptions]
|
|
725
|
+
);
|
|
726
|
+
/** A draft's days and times, so a value written another way ({to, from}) still matches. */
|
|
727
|
+
const contentOf = (dr: Draft): string => JSON.stringify(valuesOf(dr));
|
|
728
|
+
const savedContent = contentOf(saved);
|
|
729
|
+
const savedSystemRef = useRef<{
|
|
730
|
+
content: string;
|
|
731
|
+
system: 'gregory' | 'hijri';
|
|
732
|
+
} | null>(null);
|
|
733
|
+
const savedSystem =
|
|
734
|
+
savedSystemRef.current?.content === savedContent
|
|
735
|
+
? savedSystemRef.current.system
|
|
736
|
+
: outsideSystem;
|
|
737
|
+
const [draftSystem, setDraftSystem] = useState(savedSystem);
|
|
738
|
+
// The draft is rebuilt on open and on a new value; so is its calendar. A new
|
|
739
|
+
// calendarSystem prop while the user is picking starts the selection over,
|
|
740
|
+
// since a selection is one calendar's.
|
|
741
|
+
const systemSeen = useRef({ isOpen, valueKey, savedSystem });
|
|
742
|
+
useEffect(() => {
|
|
743
|
+
const seen = systemSeen.current;
|
|
744
|
+
systemSeen.current = { isOpen, valueKey, savedSystem };
|
|
745
|
+
setDraftSystem(savedSystem);
|
|
746
|
+
const systemOnly =
|
|
747
|
+
seen.isOpen === isOpen &&
|
|
748
|
+
seen.valueKey === valueKey &&
|
|
749
|
+
seen.savedSystem !== savedSystem;
|
|
750
|
+
if (systemOnly && isOpen && edited.current) {
|
|
751
|
+
draft.clear();
|
|
752
|
+
resetSelectionUi();
|
|
753
|
+
}
|
|
754
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps -- the draft's actions are stable
|
|
755
|
+
}, [isOpen, valueKey, savedSystem]);
|
|
756
|
+
useEffect(() => setDualTab(dualDefaultTab), [isOpen, dualDefaultTab]);
|
|
757
|
+
const selectionShown = draftSystem === shownSystem;
|
|
758
|
+
/** Saves a value: with the calendar it was picked in, when one can be Hijri. */
|
|
759
|
+
const emitChange = useCallback(
|
|
760
|
+
(wire: PickerValue, picked: 'gregory' | 'hijri') => {
|
|
761
|
+
savedSystemRef.current = {
|
|
762
|
+
content: contentOf(createDraft(mode, wire, draftOptions)),
|
|
763
|
+
system: picked,
|
|
764
|
+
};
|
|
765
|
+
if (hijriInPlay) onChange(wire, { calendarSystem: picked });
|
|
766
|
+
else onChange(wire);
|
|
767
|
+
},
|
|
768
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps -- contentOf reads only `type` and the options listed
|
|
769
|
+
[hijriInPlay, onChange, mode, draftOptions]
|
|
770
|
+
);
|
|
771
|
+
|
|
532
772
|
/**
|
|
533
773
|
* Whether a draft can be saved as it stands: complete, every day allowed,
|
|
534
774
|
* every time allowed and on the minute step, and nothing being typed or
|
|
@@ -555,18 +795,18 @@ const DateTimePicker: React.FC<DateTimePickerProps> = (props) => {
|
|
|
555
795
|
// Every save: Confirm, an 'instant' edit (onConfirm is added by `confirm`).
|
|
556
796
|
const commit = useCallback(
|
|
557
797
|
(wire: PickerValue) => {
|
|
558
|
-
|
|
798
|
+
emitChange(wire, draftSystem);
|
|
559
799
|
onBlur?.();
|
|
560
800
|
},
|
|
561
|
-
[
|
|
801
|
+
[emitChange, draftSystem, onBlur]
|
|
562
802
|
);
|
|
563
803
|
|
|
564
804
|
const confirm = useCallback(() => {
|
|
565
805
|
if (!canConfirm) return;
|
|
566
|
-
const { validate: check, onError: report, value:
|
|
806
|
+
const { validate: check, onError: report, value: current } = latest.current;
|
|
567
807
|
if (timeBlocked) {
|
|
568
808
|
setTimeCheck((n) => n + 1);
|
|
569
|
-
report?.(timeIssue ?? 'TIME_NOT_ALLOWED',
|
|
809
|
+
report?.(timeIssue ?? 'TIME_NOT_ALLOWED', current);
|
|
570
810
|
return;
|
|
571
811
|
}
|
|
572
812
|
const wire = draftToWire(preview, draftOptions);
|
|
@@ -579,7 +819,7 @@ const DateTimePicker: React.FC<DateTimePickerProps> = (props) => {
|
|
|
579
819
|
commit(wire);
|
|
580
820
|
onConfirm?.(wire);
|
|
581
821
|
edited.current = false;
|
|
582
|
-
if (!isInline)
|
|
822
|
+
if (!isInline) closePicker();
|
|
583
823
|
}, [
|
|
584
824
|
canConfirm,
|
|
585
825
|
timeBlocked,
|
|
@@ -589,15 +829,15 @@ const DateTimePicker: React.FC<DateTimePickerProps> = (props) => {
|
|
|
589
829
|
preview,
|
|
590
830
|
draftOptions,
|
|
591
831
|
isInline,
|
|
592
|
-
|
|
832
|
+
closePicker,
|
|
593
833
|
]);
|
|
594
834
|
|
|
595
835
|
const clearValue = useCallback(() => {
|
|
596
836
|
draft.clear();
|
|
597
837
|
resetSelectionUi();
|
|
598
838
|
edited.current = false;
|
|
599
|
-
|
|
600
|
-
}, [draft,
|
|
839
|
+
emitChange('', draftSystem);
|
|
840
|
+
}, [draft, emitChange, draftSystem, resetSelectionUi]);
|
|
601
841
|
|
|
602
842
|
const clearDraft = useCallback(() => {
|
|
603
843
|
edited.current = true;
|
|
@@ -607,10 +847,11 @@ const DateTimePicker: React.FC<DateTimePickerProps> = (props) => {
|
|
|
607
847
|
|
|
608
848
|
// × in the field above the calendar. 'instant' saves every edit, so it
|
|
609
849
|
// clears the saved value too (once; an empty value isn't saved again).
|
|
850
|
+
const hasSaved = valuesOf(saved).some((v) => v !== null);
|
|
610
851
|
const clearInSheet = useCallback(() => {
|
|
611
|
-
if (commitMode === 'instant' &&
|
|
852
|
+
if (commitMode === 'instant' && hasSaved) clearValue();
|
|
612
853
|
else clearDraft();
|
|
613
|
-
}, [commitMode,
|
|
854
|
+
}, [commitMode, hasSaved, clearValue, clearDraft]);
|
|
614
855
|
|
|
615
856
|
// Opening or closing starts without the last `validate` message.
|
|
616
857
|
useEffect(() => setValidation(null), [isOpen]);
|
|
@@ -621,15 +862,32 @@ const DateTimePicker: React.FC<DateTimePickerProps> = (props) => {
|
|
|
621
862
|
// An edit equal to the saved value isn't saved again; one `validate`
|
|
622
863
|
// rejected waits until the selection or the saved value changes.
|
|
623
864
|
const lastRejected = useRef<string | null>(null);
|
|
865
|
+
// The last value saved: not saved again while the parent's `value` catches
|
|
866
|
+
// up (a slow store, a server round trip) or when it never does.
|
|
867
|
+
const lastEmitted = useRef<string | null>(null);
|
|
624
868
|
useEffect(() => {
|
|
625
869
|
lastRejected.current = null;
|
|
626
|
-
|
|
627
|
-
|
|
870
|
+
lastEmitted.current = null;
|
|
871
|
+
}, [valueKey, isOpen]);
|
|
872
|
+
// The field in the sheet saves once typing there is done (focus leaves it),
|
|
873
|
+
// so 09:30 isn't saved as 09:00 and 09:03 on the way.
|
|
874
|
+
const sheetTyping = !!(
|
|
875
|
+
focusedParts.value ||
|
|
876
|
+
focusedParts['value-from'] ||
|
|
877
|
+
focusedParts['value-to']
|
|
878
|
+
);
|
|
879
|
+
/** Saves the draft in instant mode; `typingDone` once the sheet's field is left. */
|
|
880
|
+
const saveInstant = (typingDone: boolean) => {
|
|
628
881
|
if (commitMode !== 'instant' || !isOpen || !edited.current) return;
|
|
629
|
-
if (timeIssue || !canSave(draft.draft)) return;
|
|
882
|
+
if (!typingDone || timeIssue || !canSave(draft.draft)) return;
|
|
630
883
|
const wire = draft.toWire();
|
|
631
884
|
const key = JSON.stringify(wire);
|
|
632
|
-
if (key ===
|
|
885
|
+
if (key === lastRejected.current || key === lastEmitted.current) return;
|
|
886
|
+
// The same dates picked in the same calendar aren't saved again; the same
|
|
887
|
+
// dates picked in the other calendar are.
|
|
888
|
+
const unchanged =
|
|
889
|
+
key === valueKey || contentOf(draft.draft) === savedContent;
|
|
890
|
+
if (unchanged && draftSystem === savedSystem) return;
|
|
633
891
|
const message = latest.current.validate?.(wire);
|
|
634
892
|
if (message) {
|
|
635
893
|
lastRejected.current = key;
|
|
@@ -637,18 +895,35 @@ const DateTimePicker: React.FC<DateTimePickerProps> = (props) => {
|
|
|
637
895
|
latest.current.onError?.('VALIDATION', wire);
|
|
638
896
|
return;
|
|
639
897
|
}
|
|
898
|
+
lastEmitted.current = key;
|
|
640
899
|
commit(wire);
|
|
900
|
+
};
|
|
901
|
+
// Only typing waits for the field to be left; a tap is a finished edit.
|
|
902
|
+
const typedLast = useRef(false);
|
|
903
|
+
useEffect(
|
|
904
|
+
() => saveInstant(!sheetTyping || !typedLast.current),
|
|
641
905
|
// eslint-disable-next-line react-hooks/exhaustive-deps -- when the draft or what blocks it changes, not when callbacks are re-created
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
906
|
+
[
|
|
907
|
+
commitMode,
|
|
908
|
+
isOpen,
|
|
909
|
+
sheetTyping,
|
|
910
|
+
draft.draft,
|
|
911
|
+
draftSystem,
|
|
912
|
+
timeIssue,
|
|
913
|
+
typingBlocked,
|
|
914
|
+
wheelSpinning,
|
|
915
|
+
confirmDisabled,
|
|
916
|
+
constraints,
|
|
917
|
+
]
|
|
918
|
+
);
|
|
919
|
+
// Closing while the sheet's field still has focus ends the typing: an
|
|
920
|
+
// instant picker saves what was typed before it closes.
|
|
921
|
+
const saveInstantRef = useRef(saveInstant);
|
|
922
|
+
saveInstantRef.current = saveInstant;
|
|
923
|
+
const close = useCallback(() => {
|
|
924
|
+
saveInstantRef.current(true);
|
|
925
|
+
closePicker();
|
|
926
|
+
}, [closePicker]);
|
|
652
927
|
|
|
653
928
|
// A typed date with no time yet gets one the way a tapped day does: the fixed
|
|
654
929
|
// newDayTime, else a time already typed in the time row, else now.
|
|
@@ -664,8 +939,16 @@ const DateTimePicker: React.FC<DateTimePickerProps> = (props) => {
|
|
|
664
939
|
}
|
|
665
940
|
: null);
|
|
666
941
|
|
|
942
|
+
// Typed segments are Gregorian, so typing keeps the calendar a selection was
|
|
943
|
+
// picked in; into an empty draft, it takes the calendar on screen.
|
|
944
|
+
const adoptShownIfEmpty = () => {
|
|
945
|
+
if (valuesOf(d).every((v) => v === null)) setDraftSystem(shownSystem);
|
|
946
|
+
};
|
|
947
|
+
|
|
667
948
|
const onSheetTyped = (parsed: ParsedSegments) => {
|
|
668
949
|
edited.current = true;
|
|
950
|
+
typedLast.current = true;
|
|
951
|
+
adoptShownIfEmpty();
|
|
669
952
|
const typed = withFilledTime(parsed, 'single');
|
|
670
953
|
setTypingBlock('single', !parsed.isEmpty && !typed);
|
|
671
954
|
if (typed) draft.setValue(typed, 'single');
|
|
@@ -674,6 +957,8 @@ const DateTimePicker: React.FC<DateTimePickerProps> = (props) => {
|
|
|
674
957
|
|
|
675
958
|
const onRangeTyped = (end: RangeEnd, parsed: ParsedSegments) => {
|
|
676
959
|
edited.current = true;
|
|
960
|
+
typedLast.current = true;
|
|
961
|
+
adoptShownIfEmpty();
|
|
677
962
|
const typed = withFilledTime(parsed, end);
|
|
678
963
|
setTypingBlock(end, !parsed.isEmpty && !typed);
|
|
679
964
|
if (typed || parsed.isEmpty) draft.setValue(typed, end);
|
|
@@ -692,35 +977,111 @@ const DateTimePicker: React.FC<DateTimePickerProps> = (props) => {
|
|
|
692
977
|
return { hour: Math.floor(capped / 60), minute: capped % 60 };
|
|
693
978
|
}, [now, zone, minuteStep]);
|
|
694
979
|
|
|
980
|
+
// A range end picked on the start's day starts an hour after the start (at
|
|
981
|
+
// most the day's latest allowed time, or its last step), so the range isn't
|
|
982
|
+
// out of order before the user has touched the time.
|
|
983
|
+
// A time typed for the end before its day is kept as typed.
|
|
984
|
+
const endTimeFor = (
|
|
985
|
+
from: DateTimeValue | null,
|
|
986
|
+
day: CalendarDate,
|
|
987
|
+
typed: Time | null | undefined
|
|
988
|
+
): Time => {
|
|
989
|
+
if (!fixedTime && typed) return typed;
|
|
990
|
+
const time = fixedTime ?? nowTime();
|
|
991
|
+
if (!from?.time || !isSameDay(from.date, day)) return time;
|
|
992
|
+
const start = from.time.hour * 60 + from.time.minute;
|
|
993
|
+
if (time.hour * 60 + time.minute > start) return time;
|
|
994
|
+
// Within the day's allowed hours when it has them.
|
|
995
|
+
const max = constraints.timeBounds(day)?.max;
|
|
996
|
+
const last = max
|
|
997
|
+
? max.hour * 60 + max.minute
|
|
998
|
+
: 24 * 60 - Math.max(1, minuteStep);
|
|
999
|
+
const end = Math.min(start + 60, last);
|
|
1000
|
+
return { hour: Math.floor(end / 60), minute: end % 60 };
|
|
1001
|
+
};
|
|
1002
|
+
|
|
695
1003
|
const onSelectDay = useCallback(
|
|
696
1004
|
(day: CalendarDate) => {
|
|
697
1005
|
edited.current = true;
|
|
698
|
-
|
|
1006
|
+
typedLast.current = false;
|
|
1007
|
+
// A pick in the other calendar starts over: the selection is one calendar's.
|
|
1008
|
+
const fresh =
|
|
1009
|
+
draftSystem !== shownSystem && valuesOf(d).some((v) => v !== null);
|
|
1010
|
+
setDraftSystem(shownSystem);
|
|
1011
|
+
const cur = fresh ? emptied(d) : d;
|
|
1012
|
+
const end0: RangeEnd = fresh ? 'from' : activeEnd;
|
|
1013
|
+
const dayInEdit = fresh ? null : activeDay;
|
|
1014
|
+
const times: PendingTimes = fresh ? {} : pending;
|
|
1015
|
+
if (fresh) {
|
|
1016
|
+
draft.clear();
|
|
1017
|
+
resetSelectionUi();
|
|
1018
|
+
}
|
|
1019
|
+
if (cur.mode === 'range') {
|
|
699
1020
|
if (rangeLayout === 'fromTo') {
|
|
1021
|
+
setEndPicked(false);
|
|
1022
|
+
// A full range: the tap starts a new one (or moves the nearest end).
|
|
1023
|
+
if (!endPicked && cur.from.value && cur.to.value) {
|
|
1024
|
+
const moveNearest = range?.reselect === 'moveNearestEnd';
|
|
1025
|
+
const nearest: RangeEnd =
|
|
1026
|
+
Math.abs(daysBetween(cur.from.value.date, day)) <
|
|
1027
|
+
Math.abs(daysBetween(cur.to.value.date, day))
|
|
1028
|
+
? 'from'
|
|
1029
|
+
: 'to';
|
|
1030
|
+
const end = moveNearest ? nearest : 'from';
|
|
1031
|
+
setTypingBlock('from', false);
|
|
1032
|
+
setTypingBlock('to', false);
|
|
1033
|
+
draft.selectDay(day, fixedTime ?? times[end] ?? nowTime());
|
|
1034
|
+
setActiveEnd(moveNearest ? nearest : 'to');
|
|
1035
|
+
return;
|
|
1036
|
+
}
|
|
1037
|
+
// Choosing the end after a start tap: a day before the start moves the start.
|
|
1038
|
+
const end1: RangeEnd =
|
|
1039
|
+
end0 === 'to' &&
|
|
1040
|
+
!endPicked &&
|
|
1041
|
+
cur.from.value &&
|
|
1042
|
+
compareCalendarDates(day, cur.from.value.date) < 0
|
|
1043
|
+
? 'from'
|
|
1044
|
+
: end0;
|
|
700
1045
|
// The tapped day replaces whatever was typed in this end only.
|
|
701
|
-
setTypingBlock(
|
|
1046
|
+
setTypingBlock(end1, false);
|
|
702
1047
|
draft.setRangeEnd(
|
|
703
1048
|
day,
|
|
704
|
-
|
|
705
|
-
|
|
1049
|
+
end1,
|
|
1050
|
+
end1 === 'to'
|
|
1051
|
+
? endTimeFor(cur.from.value, day, times.to)
|
|
1052
|
+
: (fixedTime ?? times.from ?? nowTime())
|
|
706
1053
|
);
|
|
707
|
-
|
|
1054
|
+
// The start tapped again where a range can't be one day deselects
|
|
1055
|
+
// it, so the next tap picks the start again.
|
|
1056
|
+
const deselects =
|
|
1057
|
+
end1 === 'to' &&
|
|
1058
|
+
!sameDayRangeOk(draftOptions) &&
|
|
1059
|
+
isSameDay(cur.from.value?.date, day);
|
|
1060
|
+
if (end0 === 'from') setActiveEnd('to');
|
|
1061
|
+
else if (deselects) setActiveEnd('from');
|
|
708
1062
|
return;
|
|
709
1063
|
}
|
|
710
1064
|
// The single field: the first tap is the start, the next one the end.
|
|
711
1065
|
const startsOver =
|
|
712
|
-
!
|
|
713
|
-
(!!
|
|
1066
|
+
!cur.from.value ||
|
|
1067
|
+
(!!cur.to.value && range?.reselect !== 'moveNearestEnd');
|
|
714
1068
|
const end: RangeEnd = startsOver ? 'from' : 'to';
|
|
715
1069
|
setTypingBlock('single', false);
|
|
716
|
-
draft.selectDay(
|
|
1070
|
+
draft.selectDay(
|
|
1071
|
+
day,
|
|
1072
|
+
end === 'to' && !cur.to.value
|
|
1073
|
+
? endTimeFor(cur.from.value, day, times.to)
|
|
1074
|
+
: (fixedTime ?? times[end] ?? nowTime())
|
|
1075
|
+
);
|
|
717
1076
|
return;
|
|
718
1077
|
}
|
|
719
|
-
if (
|
|
1078
|
+
if (cur.mode === 'multiple') {
|
|
720
1079
|
const perDate = type === 'datetime' && multipleTimeMode === 'perDate';
|
|
721
|
-
const picked =
|
|
1080
|
+
const picked = cur.slots.some((slot) =>
|
|
1081
|
+
isSameDay(slot.value?.date, day)
|
|
1082
|
+
);
|
|
722
1083
|
// perDate: a tap on a picked day first makes it the one the time row edits.
|
|
723
|
-
if (perDate && picked && !isSameDay(
|
|
1084
|
+
if (perDate && picked && !isSameDay(dayInEdit, day)) {
|
|
724
1085
|
setActiveDay(day);
|
|
725
1086
|
return;
|
|
726
1087
|
}
|
|
@@ -729,8 +1090,9 @@ const DateTimePicker: React.FC<DateTimePickerProps> = (props) => {
|
|
|
729
1090
|
return;
|
|
730
1091
|
}
|
|
731
1092
|
setTypingBlock('single', false);
|
|
732
|
-
draft.selectDay(day, fixedTime ??
|
|
1093
|
+
draft.selectDay(day, fixedTime ?? times.single ?? nowTime());
|
|
733
1094
|
},
|
|
1095
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps -- valuesOf reads only `d` and props listed here
|
|
734
1096
|
[
|
|
735
1097
|
d,
|
|
736
1098
|
draft,
|
|
@@ -744,21 +1106,39 @@ const DateTimePicker: React.FC<DateTimePickerProps> = (props) => {
|
|
|
744
1106
|
type,
|
|
745
1107
|
multipleTimeMode,
|
|
746
1108
|
setTypingBlock,
|
|
1109
|
+
draftSystem,
|
|
1110
|
+
shownSystem,
|
|
1111
|
+
resetSelectionUi,
|
|
1112
|
+
endPicked,
|
|
1113
|
+
draftOptions,
|
|
1114
|
+
minuteStep,
|
|
1115
|
+
constraints,
|
|
747
1116
|
]
|
|
748
1117
|
);
|
|
749
1118
|
|
|
1119
|
+
// The calendar gets a tap handler that never changes, so its memoized day
|
|
1120
|
+
// cells don't all redraw on every tap; it calls the current one.
|
|
1121
|
+
const selectDayRef = useRef(onSelectDay);
|
|
1122
|
+
selectDayRef.current = onSelectDay;
|
|
1123
|
+
const onSelectDayStable = useCallback(
|
|
1124
|
+
(day: CalendarDate) => selectDayRef.current(day),
|
|
1125
|
+
[]
|
|
1126
|
+
);
|
|
1127
|
+
|
|
750
1128
|
// Greying that depends on what's picked: range length, From/To order, the multiple cap.
|
|
751
1129
|
const calendarConstraints = useMemo(() => {
|
|
752
1130
|
if (d.mode === 'range') {
|
|
1131
|
+
// A full From/To range greys nothing: the next tap starts a new one.
|
|
753
1132
|
const choosingEnd =
|
|
754
1133
|
rangeLayout === 'fromTo'
|
|
755
|
-
? activeEnd === 'to'
|
|
1134
|
+
? activeEnd === 'to' && (endPicked || !d.to.value)
|
|
756
1135
|
: !!d.from.value && !d.to.value;
|
|
757
1136
|
return withSelectionRules(constraints, {
|
|
758
1137
|
rangeStart: choosingEnd ? d.from.value?.date : null,
|
|
759
1138
|
minDays: range?.minDays,
|
|
760
1139
|
maxDays: range?.maxDays,
|
|
761
|
-
|
|
1140
|
+
// Only an end chosen from the To field can't go before the start.
|
|
1141
|
+
blockBeforeStart: rangeLayout === 'fromTo' && endPicked,
|
|
762
1142
|
});
|
|
763
1143
|
}
|
|
764
1144
|
if (d.mode === 'multiple') {
|
|
@@ -775,6 +1155,7 @@ const DateTimePicker: React.FC<DateTimePickerProps> = (props) => {
|
|
|
775
1155
|
d,
|
|
776
1156
|
rangeLayout,
|
|
777
1157
|
activeEnd,
|
|
1158
|
+
endPicked,
|
|
778
1159
|
range?.minDays,
|
|
779
1160
|
range?.maxDays,
|
|
780
1161
|
multiple?.maxSelections,
|
|
@@ -812,15 +1193,16 @@ const DateTimePicker: React.FC<DateTimePickerProps> = (props) => {
|
|
|
812
1193
|
|
|
813
1194
|
const handleVisibleMonth = useCallback(
|
|
814
1195
|
(year: number, month: number) => {
|
|
815
|
-
|
|
1196
|
+
loadHolidaysAround(year, month);
|
|
816
1197
|
onVisibleMonthChange?.(year, month);
|
|
817
1198
|
},
|
|
818
|
-
[
|
|
1199
|
+
[loadHolidaysAround, onVisibleMonthChange]
|
|
819
1200
|
);
|
|
820
1201
|
useEffect(() => {
|
|
821
1202
|
if (!isOpen) return;
|
|
822
|
-
const anchor =
|
|
823
|
-
|
|
1203
|
+
const anchor =
|
|
1204
|
+
(selection.kind === 'single' ? selection.day : null) ?? today;
|
|
1205
|
+
loadHolidaysAround(anchor.year, anchor.month);
|
|
824
1206
|
// eslint-disable-next-line react-hooks/exhaustive-deps -- once per open
|
|
825
1207
|
}, [isOpen]);
|
|
826
1208
|
|
|
@@ -874,17 +1256,28 @@ const DateTimePicker: React.FC<DateTimePickerProps> = (props) => {
|
|
|
874
1256
|
return;
|
|
875
1257
|
}
|
|
876
1258
|
setTriggerError('');
|
|
877
|
-
|
|
1259
|
+
// Typing keeps the calendar the saved value was picked in.
|
|
1260
|
+
emitChange(wire, savedSystem);
|
|
878
1261
|
onBlur?.();
|
|
879
1262
|
};
|
|
880
1263
|
|
|
1264
|
+
/** A value's date in the calendar it was picked in: Hijri text, or none for the dateFormat. */
|
|
1265
|
+
const dateTextFor = (
|
|
1266
|
+
picked: 'gregory' | 'hijri'
|
|
1267
|
+
): DateTextFormatter | undefined =>
|
|
1268
|
+
picked === 'hijri'
|
|
1269
|
+
? (v) =>
|
|
1270
|
+
hijriDateText(v.date, hijriLocale) ?? formatDateText(v, dateFormat)
|
|
1271
|
+
: undefined;
|
|
1272
|
+
const savedDateText = dateTextFor(savedSystem);
|
|
1273
|
+
const draftDateText = dateTextFor(draftSystem);
|
|
1274
|
+
const savedStrings = stringsFor(savedSystem);
|
|
1275
|
+
const draftStrings = stringsFor(draftSystem);
|
|
1276
|
+
// Error messages inside the picker follow the tab on screen; the on-screen
|
|
1277
|
+
// field's keep `strings`.
|
|
1278
|
+
const shownStrings = stringsFor(shownSystem);
|
|
1279
|
+
|
|
881
1280
|
// What the on-screen field shows: the saved value, never the draft.
|
|
882
|
-
const savedKey = valueKey;
|
|
883
|
-
const saved = useMemo(
|
|
884
|
-
() => createDraft(mode, value, draftOptions),
|
|
885
|
-
// eslint-disable-next-line react-hooks/exhaustive-deps -- the value is compared by content (`savedKey`)
|
|
886
|
-
[savedKey, mode, draftOptions]
|
|
887
|
-
);
|
|
888
1281
|
const savedSingle = saved.mode === 'single' ? saved.slot.value : null;
|
|
889
1282
|
const savedText =
|
|
890
1283
|
saved.mode === 'range'
|
|
@@ -894,14 +1287,18 @@ const DateTimePicker: React.FC<DateTimePickerProps> = (props) => {
|
|
|
894
1287
|
type,
|
|
895
1288
|
dateFormat,
|
|
896
1289
|
timeFormat,
|
|
897
|
-
strings.rangeSeparator
|
|
1290
|
+
strings.rangeSeparator,
|
|
1291
|
+
savedDateText,
|
|
1292
|
+
savedStrings
|
|
898
1293
|
)
|
|
899
1294
|
: saved.mode === 'multiple'
|
|
900
1295
|
? formatMultipleText(
|
|
901
1296
|
saved.slots.map((slot) => slot.value),
|
|
902
1297
|
type,
|
|
903
1298
|
dateFormat,
|
|
904
|
-
timeFormat
|
|
1299
|
+
timeFormat,
|
|
1300
|
+
savedDateText,
|
|
1301
|
+
savedStrings
|
|
905
1302
|
)
|
|
906
1303
|
: precision !== 'day'
|
|
907
1304
|
? periodText(savedSingle)
|
|
@@ -954,8 +1351,17 @@ const DateTimePicker: React.FC<DateTimePickerProps> = (props) => {
|
|
|
954
1351
|
type,
|
|
955
1352
|
mode,
|
|
956
1353
|
value,
|
|
957
|
-
displayText:
|
|
958
|
-
savedText ??
|
|
1354
|
+
displayText: digitsFor(savedSystem)(
|
|
1355
|
+
savedText ??
|
|
1356
|
+
formatValueText(
|
|
1357
|
+
savedSingle,
|
|
1358
|
+
type,
|
|
1359
|
+
dateFormat,
|
|
1360
|
+
timeFormat,
|
|
1361
|
+
savedDateText,
|
|
1362
|
+
savedStrings
|
|
1363
|
+
)
|
|
1364
|
+
),
|
|
959
1365
|
disabled,
|
|
960
1366
|
readOnly,
|
|
961
1367
|
isOpen,
|
|
@@ -981,7 +1387,7 @@ const DateTimePicker: React.FC<DateTimePickerProps> = (props) => {
|
|
|
981
1387
|
draft.draft.mode === 'single' ? draft.draft.slot.value : null;
|
|
982
1388
|
// A time outside the limits is explained under the time row, not here.
|
|
983
1389
|
const sheetValueError =
|
|
984
|
-
sheetValue && dayOff(sheetValue.date) ?
|
|
1390
|
+
sheetValue && dayOff(sheetValue.date) ? shownStrings.errorNotAllowed : '';
|
|
985
1391
|
|
|
986
1392
|
let rangeError = '';
|
|
987
1393
|
if (d.mode === 'range' && d.from.value && d.to.value) {
|
|
@@ -990,14 +1396,14 @@ const DateTimePicker: React.FC<DateTimePickerProps> = (props) => {
|
|
|
990
1396
|
constraints.dayStatus(d.from.value.date).disabled ||
|
|
991
1397
|
constraints.dayStatus(d.to.value.date).disabled
|
|
992
1398
|
)
|
|
993
|
-
rangeError =
|
|
1399
|
+
rangeError = shownStrings.errorNotAllowed;
|
|
994
1400
|
else if (previewIssue === 'RANGE_ORDER')
|
|
995
|
-
rangeError =
|
|
1401
|
+
rangeError = shownStrings.errorRangeOrder;
|
|
996
1402
|
else if (previewIssue === 'RANGE_LENGTH')
|
|
997
1403
|
rangeError =
|
|
998
1404
|
range?.minDays !== undefined && length < range.minDays
|
|
999
|
-
?
|
|
1000
|
-
:
|
|
1405
|
+
? shownStrings.errorRangeTooShort(range.minDays)
|
|
1406
|
+
: shownStrings.errorRangeTooLong(range?.maxDays ?? length);
|
|
1001
1407
|
}
|
|
1002
1408
|
|
|
1003
1409
|
const fieldCommon = {
|
|
@@ -1019,8 +1425,11 @@ const DateTimePicker: React.FC<DateTimePickerProps> = (props) => {
|
|
|
1019
1425
|
) : (
|
|
1020
1426
|
<ValueField
|
|
1021
1427
|
{...fieldCommon}
|
|
1428
|
+
strings={savedStrings}
|
|
1429
|
+
errorStrings={strings}
|
|
1022
1430
|
value={savedSingle}
|
|
1023
1431
|
displayText={savedText}
|
|
1432
|
+
dateText={savedDateText}
|
|
1024
1433
|
allowTyping={
|
|
1025
1434
|
mode === 'single' && precision === 'day' && allowFieldTyping
|
|
1026
1435
|
}
|
|
@@ -1062,8 +1471,11 @@ const DateTimePicker: React.FC<DateTimePickerProps> = (props) => {
|
|
|
1062
1471
|
) : mode === 'single' ? (
|
|
1063
1472
|
<ValueField
|
|
1064
1473
|
{...fieldCommon}
|
|
1474
|
+
strings={draftStrings}
|
|
1475
|
+
errorStrings={shownStrings}
|
|
1065
1476
|
value={sheetValue}
|
|
1066
1477
|
displayText={precision !== 'day' ? periodText(sheetValue) : undefined}
|
|
1478
|
+
dateText={draftDateText}
|
|
1067
1479
|
placeholder={sheetOptions?.fieldPlaceholder ?? periodPlaceholder}
|
|
1068
1480
|
allowTyping={allowTyping && precision === 'day'}
|
|
1069
1481
|
onTyped={onSheetTyped}
|
|
@@ -1078,13 +1490,16 @@ const DateTimePicker: React.FC<DateTimePickerProps> = (props) => {
|
|
|
1078
1490
|
) : d.mode === 'range' ? (
|
|
1079
1491
|
<RangeFields
|
|
1080
1492
|
{...fieldCommon}
|
|
1493
|
+
strings={draftStrings}
|
|
1494
|
+
errorStrings={shownStrings}
|
|
1081
1495
|
layout={rangeLayout}
|
|
1496
|
+
dateText={draftDateText}
|
|
1082
1497
|
showIcon={sheetOptions?.fieldIcon}
|
|
1083
1498
|
from={d.from.value}
|
|
1084
1499
|
to={d.to.value}
|
|
1085
1500
|
placeholder={sheetOptions?.fieldPlaceholder}
|
|
1086
1501
|
activeEnd={activeEnd}
|
|
1087
|
-
onActivate={
|
|
1502
|
+
onActivate={activateEnd}
|
|
1088
1503
|
allowTyping={allowTyping}
|
|
1089
1504
|
onTyped={onRangeTyped}
|
|
1090
1505
|
onFocusChange={(end, f) => reportFocus(`value-${end}`, f)}
|
|
@@ -1097,12 +1512,15 @@ const DateTimePicker: React.FC<DateTimePickerProps> = (props) => {
|
|
|
1097
1512
|
) : d.mode === 'multiple' ? (
|
|
1098
1513
|
<ValueField
|
|
1099
1514
|
{...fieldCommon}
|
|
1515
|
+
strings={draftStrings}
|
|
1100
1516
|
value={null}
|
|
1101
1517
|
displayText={formatMultipleText(
|
|
1102
1518
|
draftValues,
|
|
1103
1519
|
type,
|
|
1104
1520
|
dateFormat,
|
|
1105
|
-
timeFormat
|
|
1521
|
+
timeFormat,
|
|
1522
|
+
draftDateText,
|
|
1523
|
+
draftStrings
|
|
1106
1524
|
)}
|
|
1107
1525
|
placeholder={sheetOptions?.fieldPlaceholder}
|
|
1108
1526
|
allowClear={allowClear}
|
|
@@ -1114,41 +1532,114 @@ const DateTimePicker: React.FC<DateTimePickerProps> = (props) => {
|
|
|
1114
1532
|
/>
|
|
1115
1533
|
) : null;
|
|
1116
1534
|
|
|
1117
|
-
const
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
|
|
1144
|
-
|
|
1145
|
-
|
|
1535
|
+
const systemLabels: Record<CalendarSystem, string> = {
|
|
1536
|
+
gregory: strings.calendarGregorian ?? DEFAULT_STRINGS.calendarGregorian,
|
|
1537
|
+
hijri: strings.calendarHijri ?? DEFAULT_STRINGS.calendarHijri,
|
|
1538
|
+
dual: strings.calendarDual ?? DEFAULT_STRINGS.calendarDual,
|
|
1539
|
+
};
|
|
1540
|
+
const systemA11yLabel =
|
|
1541
|
+
strings.a11yCalendarSystem ?? DEFAULT_STRINGS.a11yCalendarSystem;
|
|
1542
|
+
const chooseSystem = (next: CalendarSystem) => {
|
|
1543
|
+
if (next === system) return;
|
|
1544
|
+
setChosenSystem(next);
|
|
1545
|
+
if (next === 'dual') setDualTab(dualDefaultTab);
|
|
1546
|
+
echo.current = next;
|
|
1547
|
+
onCalendarSystemChange?.(next);
|
|
1548
|
+
};
|
|
1549
|
+
const calendarSwitchNode = !switchSystems ? null : renderCalendarSwitch ? (
|
|
1550
|
+
renderCalendarSwitch({
|
|
1551
|
+
systems: switchSystems,
|
|
1552
|
+
selected: system,
|
|
1553
|
+
onSelect: chooseSystem,
|
|
1554
|
+
tokens,
|
|
1555
|
+
strings,
|
|
1556
|
+
})
|
|
1557
|
+
) : (
|
|
1558
|
+
<CalendarSwitch
|
|
1559
|
+
variant="switch"
|
|
1560
|
+
options={switchSystems.map((v) => {
|
|
1561
|
+
return { value: v, label: systemLabels[v] };
|
|
1562
|
+
})}
|
|
1563
|
+
selected={system}
|
|
1564
|
+
onSelect={chooseSystem}
|
|
1565
|
+
accessibilityLabel={systemA11yLabel}
|
|
1146
1566
|
tokens={tokens}
|
|
1147
1567
|
styles={styles}
|
|
1148
|
-
|
|
1149
|
-
testID={testID}
|
|
1568
|
+
testID={testID ? `${testID}-calendar-switch` : undefined}
|
|
1150
1569
|
/>
|
|
1151
1570
|
);
|
|
1571
|
+
const dualTabs =
|
|
1572
|
+
system !== 'dual' ? null : (
|
|
1573
|
+
<CalendarSwitch
|
|
1574
|
+
variant="tabs"
|
|
1575
|
+
options={[
|
|
1576
|
+
{ value: 'gregory', label: systemLabels.gregory },
|
|
1577
|
+
{ value: 'hijri', label: systemLabels.hijri },
|
|
1578
|
+
]}
|
|
1579
|
+
selected={dualTab}
|
|
1580
|
+
onSelect={setDualTab}
|
|
1581
|
+
accessibilityLabel={systemA11yLabel}
|
|
1582
|
+
tokens={tokens}
|
|
1583
|
+
styles={styles}
|
|
1584
|
+
testID={testID ? `${testID}-dual-tabs` : undefined}
|
|
1585
|
+
/>
|
|
1586
|
+
);
|
|
1587
|
+
// The Hijri tab lays out its calendar and time rows right to left; the
|
|
1588
|
+
// wrapper always renders, so a tab change doesn't remount the time boxes.
|
|
1589
|
+
const hijriShown = shownSystem === 'hijri';
|
|
1590
|
+
const mirrored = (node: React.ReactNode) =>
|
|
1591
|
+
node == null ? null : (
|
|
1592
|
+
<RtlContext.Provider value={hijriShown || null}>
|
|
1593
|
+
<View style={hijriShown ? RTL : undefined}>{node}</View>
|
|
1594
|
+
</RtlContext.Provider>
|
|
1595
|
+
);
|
|
1596
|
+
// Another calendar opens on the days the last one showed, else on the
|
|
1597
|
+
// selection (shown or not), else where the picker would open.
|
|
1598
|
+
const calendarAnchor =
|
|
1599
|
+
shownAnchor.current ??
|
|
1600
|
+
(selectionShown ? null : firstSelectedDay(selection)) ??
|
|
1601
|
+
initialVisibleDate;
|
|
1602
|
+
|
|
1603
|
+
const calendar = (
|
|
1604
|
+
<View style={folded ? FOLDED : undefined}>
|
|
1605
|
+
{calendarSwitchNode}
|
|
1606
|
+
{dualTabs}
|
|
1607
|
+
{mirrored(
|
|
1608
|
+
<Calendar
|
|
1609
|
+
key={shownSystem}
|
|
1610
|
+
constraints={selectionShown ? calendarConstraints : constraints}
|
|
1611
|
+
today={today}
|
|
1612
|
+
initialVisibleDate={calendarAnchor}
|
|
1613
|
+
selection={selectionShown ? selection : emptySelection(selection)}
|
|
1614
|
+
onSelectDay={onSelectDayStable}
|
|
1615
|
+
firstDayOfWeek={firstDayOfWeek}
|
|
1616
|
+
showOutsideDays={showOutsideDays}
|
|
1617
|
+
showHolidays={showHolidays}
|
|
1618
|
+
yearRange={yearRange}
|
|
1619
|
+
locale={shownSystem === 'hijri' ? hijriLocale : loc}
|
|
1620
|
+
calendarSystem={shownSystem}
|
|
1621
|
+
initialView={initialView}
|
|
1622
|
+
precision={precision}
|
|
1623
|
+
enableMonthSwipe={enableMonthSwipe}
|
|
1624
|
+
showToday={showToday}
|
|
1625
|
+
onVisibleMonthChange={handleVisibleMonth}
|
|
1626
|
+
onAnchorChange={rememberAnchor}
|
|
1627
|
+
renderDay={renderDay}
|
|
1628
|
+
renderWeekday={renderWeekday}
|
|
1629
|
+
renderMonthChip={renderMonthChip}
|
|
1630
|
+
renderYearCell={renderYearCell}
|
|
1631
|
+
renderMonthCell={renderMonthCell}
|
|
1632
|
+
renderEmptyState={renderEmptyState}
|
|
1633
|
+
icons={icons}
|
|
1634
|
+
iconProps={iconProps}
|
|
1635
|
+
tokens={tokens}
|
|
1636
|
+
styles={styles}
|
|
1637
|
+
strings={stringsFor(shownSystem)}
|
|
1638
|
+
testID={testID}
|
|
1639
|
+
/>
|
|
1640
|
+
)}
|
|
1641
|
+
</View>
|
|
1642
|
+
);
|
|
1152
1643
|
|
|
1153
1644
|
const timeRow =
|
|
1154
1645
|
type !== 'datetime' ? null : renderTimeRow ? (
|
|
@@ -1179,7 +1670,7 @@ const DateTimePicker: React.FC<DateTimePickerProps> = (props) => {
|
|
|
1179
1670
|
onPendingChange={onRowPending}
|
|
1180
1671
|
tokens={tokens}
|
|
1181
1672
|
styles={styles}
|
|
1182
|
-
strings={
|
|
1673
|
+
strings={stringsFor(shownSystem)}
|
|
1183
1674
|
testID={testID}
|
|
1184
1675
|
/>
|
|
1185
1676
|
);
|
|
@@ -1192,11 +1683,19 @@ const DateTimePicker: React.FC<DateTimePickerProps> = (props) => {
|
|
|
1192
1683
|
? validation.message
|
|
1193
1684
|
: null;
|
|
1194
1685
|
|
|
1686
|
+
// The field writes the date in the digits of the calendar it was picked in;
|
|
1687
|
+
// the calendar and the time rows follow the tab that shows.
|
|
1195
1688
|
const sheetContent = (
|
|
1196
1689
|
<>
|
|
1197
|
-
{
|
|
1198
|
-
|
|
1199
|
-
|
|
1690
|
+
<NumeralsContext.Provider value={digitsFor(draftSystem)}>
|
|
1691
|
+
{sheetField}
|
|
1692
|
+
</NumeralsContext.Provider>
|
|
1693
|
+
<NumeralsContext.Provider value={digitsFor(shownSystem)}>
|
|
1694
|
+
{calendar}
|
|
1695
|
+
</NumeralsContext.Provider>
|
|
1696
|
+
<NumeralsContext.Provider value={digitsFor(shownSystem)}>
|
|
1697
|
+
{mirrored(timeRow)}
|
|
1698
|
+
</NumeralsContext.Provider>
|
|
1200
1699
|
{validationMessage ? (
|
|
1201
1700
|
<FieldMessages
|
|
1202
1701
|
error={validationMessage}
|
|
@@ -1212,7 +1711,7 @@ const DateTimePicker: React.FC<DateTimePickerProps> = (props) => {
|
|
|
1212
1711
|
);
|
|
1213
1712
|
|
|
1214
1713
|
return (
|
|
1215
|
-
|
|
1714
|
+
<NumeralsContext.Provider value={digitsFor(savedSystem)}>
|
|
1216
1715
|
{trigger}
|
|
1217
1716
|
<PickerContainer
|
|
1218
1717
|
presentation={presentation}
|
|
@@ -1235,7 +1734,7 @@ const DateTimePicker: React.FC<DateTimePickerProps> = (props) => {
|
|
|
1235
1734
|
>
|
|
1236
1735
|
{sheetContent}
|
|
1237
1736
|
</PickerContainer>
|
|
1238
|
-
|
|
1737
|
+
</NumeralsContext.Provider>
|
|
1239
1738
|
);
|
|
1240
1739
|
};
|
|
1241
1740
|
|