rat_text/
date_input.rs

1//!
2//! Date-input widget using [chrono](https://docs.rs/chrono/latest/chrono/)
3//!
4
5use crate::_private::NonExhaustive;
6use crate::clipboard::Clipboard;
7use crate::event::{ReadOnly, TextOutcome};
8use crate::text_input_mask::{MaskedInput, MaskedInputState};
9use crate::undo_buffer::{UndoBuffer, UndoEntry};
10use crate::{
11    HasScreenCursor, TextError, TextFocusGained, TextFocusLost, TextStyle, TextTab, upos_type,
12};
13use chrono::NaiveDate;
14use chrono::format::{Fixed, Item, Numeric, Pad, StrftimeItems};
15use rat_event::{HandleEvent, MouseOnly, Regular};
16use rat_focus::{FocusBuilder, FocusFlag, HasFocus, Navigation};
17use rat_reloc::RelocatableState;
18use ratatui::buffer::Buffer;
19use ratatui::layout::Rect;
20use ratatui::style::Style;
21use ratatui::widgets::Block;
22use ratatui::widgets::StatefulWidget;
23use std::fmt;
24use std::ops::Range;
25use unicode_segmentation::UnicodeSegmentation;
26
27/// Widget for dates.
28///
29/// # Stateful
30/// This widget implements [`StatefulWidget`], you can use it with
31/// [`DateInputState`] to handle common actions.
32#[derive(Debug, Default, Clone)]
33pub struct DateInput<'a> {
34    widget: MaskedInput<'a>,
35}
36
37/// State & event-handling.
38/// Use [DateInputState::with_pattern] to set the date pattern.
39#[derive(Debug, Clone)]
40pub struct DateInputState {
41    /// Area of the widget
42    /// __read only__ renewed with each render.
43    pub area: Rect,
44    /// Area inside the block.
45    /// __read only__ renewed with each render.
46    pub inner: Rect,
47    /// Uses MaskedInputState for the actual functionality.
48    pub widget: MaskedInputState,
49    /// The chrono format pattern.
50    pattern: String,
51    /// Locale
52    locale: chrono::Locale,
53
54    pub non_exhaustive: NonExhaustive,
55}
56
57impl<'a> DateInput<'a> {
58    pub fn new() -> Self {
59        Self::default()
60    }
61
62    /// Show the compact form, if the focus is not with this widget.
63    #[inline]
64    pub fn compact(mut self, compact: bool) -> Self {
65        self.widget = self.widget.compact(compact);
66        self
67    }
68
69    /// Set the combined style.
70    #[inline]
71    pub fn styles(mut self, style: TextStyle) -> Self {
72        self.widget = self.widget.styles(style);
73        self
74    }
75
76    /// Base text style.
77    #[inline]
78    pub fn style(mut self, style: impl Into<Style>) -> Self {
79        self.widget = self.widget.style(style);
80        self
81    }
82
83    /// Style when focused.
84    #[inline]
85    pub fn focus_style(mut self, style: impl Into<Style>) -> Self {
86        self.widget = self.widget.focus_style(style);
87        self
88    }
89
90    /// Style for selection
91    #[inline]
92    pub fn select_style(mut self, style: impl Into<Style>) -> Self {
93        self.widget = self.widget.select_style(style);
94        self
95    }
96
97    /// Style for the invalid indicator.
98    #[inline]
99    pub fn invalid_style(mut self, style: impl Into<Style>) -> Self {
100        self.widget = self.widget.invalid_style(style);
101        self
102    }
103
104    /// Block
105    #[inline]
106    pub fn block(mut self, block: Block<'a>) -> Self {
107        self.widget = self.widget.block(block);
108        self
109    }
110
111    /// Focus behaviour
112    #[inline]
113    pub fn on_focus_gained(mut self, of: TextFocusGained) -> Self {
114        self.widget = self.widget.on_focus_gained(of);
115        self
116    }
117
118    /// Focus behaviour
119    #[inline]
120    pub fn on_focus_lost(mut self, of: TextFocusLost) -> Self {
121        self.widget = self.widget.on_focus_lost(of);
122        self
123    }
124
125    /// `Tab` behaviour
126    #[inline]
127    pub fn on_tab(mut self, of: TextTab) -> Self {
128        self.widget = self.widget.on_tab(of);
129        self
130    }
131
132    /// Preferred width.
133    pub fn width(&self, state: &DateInputState) -> u16 {
134        state.widget.mask.len() as u16 + 1
135    }
136
137    /// Preferred width.
138    pub fn height(&self) -> u16 {
139        1
140    }
141}
142
143impl<'a> StatefulWidget for &DateInput<'a> {
144    type State = DateInputState;
145
146    fn render(self, area: Rect, buf: &mut Buffer, state: &mut Self::State) {
147        (&self.widget).render(area, buf, &mut state.widget);
148
149        state.area = state.widget.area;
150        state.inner = state.widget.inner;
151    }
152}
153
154impl StatefulWidget for DateInput<'_> {
155    type State = DateInputState;
156
157    fn render(self, area: Rect, buf: &mut Buffer, state: &mut Self::State) {
158        self.widget.render(area, buf, &mut state.widget);
159
160        state.area = state.widget.area;
161        state.inner = state.widget.inner;
162    }
163}
164
165impl Default for DateInputState {
166    fn default() -> Self {
167        Self {
168            area: Default::default(),
169            inner: Default::default(),
170            widget: Default::default(),
171            pattern: Default::default(),
172            locale: Default::default(),
173            non_exhaustive: NonExhaustive,
174        }
175    }
176}
177
178impl HasFocus for DateInputState {
179    fn build(&self, builder: &mut FocusBuilder) {
180        builder.leaf_widget(self);
181    }
182
183    #[inline]
184    fn focus(&self) -> FocusFlag {
185        self.widget.focus.clone()
186    }
187
188    #[inline]
189    fn area(&self) -> Rect {
190        self.widget.area
191    }
192
193    #[inline]
194    fn navigable(&self) -> Navigation {
195        self.widget.navigable()
196    }
197}
198
199impl DateInputState {
200    /// New state.
201    pub fn new() -> Self {
202        Self::default()
203    }
204
205    pub fn named(name: &str) -> Self {
206        let mut z = Self::default();
207        z.widget.focus = z.widget.focus.with_name(name);
208        z
209    }
210
211    /// New state with a chrono date pattern.
212    pub fn with_pattern<S: AsRef<str>>(mut self, pattern: S) -> Result<Self, fmt::Error> {
213        self.set_format(pattern)?;
214        Ok(self)
215    }
216
217    /// New state with a localized chrono date pattern.
218    #[inline]
219    pub fn with_loc_pattern<S: AsRef<str>>(
220        mut self,
221        pattern: S,
222        locale: chrono::Locale,
223    ) -> Result<Self, fmt::Error> {
224        self.set_format_loc(pattern, locale)?;
225        Ok(self)
226    }
227
228    /// chrono format string.
229    #[inline]
230    pub fn format(&self) -> &str {
231        self.pattern.as_str()
232    }
233
234    /// chrono locale.
235    #[inline]
236    pub fn locale(&self) -> chrono::Locale {
237        self.locale
238    }
239
240    /// chrono format string.
241    ///
242    /// generates a mask according to the format and overwrites whatever
243    /// set_mask() did.
244    #[inline]
245    pub fn set_format<S: AsRef<str>>(&mut self, pattern: S) -> Result<(), fmt::Error> {
246        self.set_format_loc(pattern, chrono::Locale::default())
247    }
248
249    /// chrono format string.
250    ///
251    /// generates a mask according to the format and overwrites whatever
252    /// set_mask() did.
253    #[inline]
254    pub fn set_format_loc<S: AsRef<str>>(
255        &mut self,
256        pattern: S,
257        locale: chrono::Locale,
258    ) -> Result<(), fmt::Error> {
259        let mut mask = String::new();
260        let items = StrftimeItems::new_with_locale(pattern.as_ref(), locale)
261            .parse()
262            .map_err(|_| fmt::Error)?;
263        for t in &items {
264            match t {
265                Item::Literal(s) => {
266                    for c in s.graphemes(true) {
267                        mask.push('\\');
268                        mask.push_str(c);
269                    }
270                }
271                Item::OwnedLiteral(s) => {
272                    for c in s.graphemes(true) {
273                        mask.push('\\');
274                        mask.push_str(c);
275                    }
276                }
277                Item::Space(s) => {
278                    for c in s.graphemes(true) {
279                        mask.push_str(c);
280                    }
281                }
282                Item::OwnedSpace(s) => {
283                    for c in s.graphemes(true) {
284                        mask.push_str(c);
285                    }
286                }
287                Item::Numeric(v, Pad::None | Pad::Space) => match v {
288                    Numeric::Year | Numeric::IsoYear => mask.push_str("9999"),
289                    Numeric::YearDiv100
290                    | Numeric::YearMod100
291                    | Numeric::IsoYearDiv100
292                    | Numeric::IsoYearMod100
293                    | Numeric::Month
294                    | Numeric::Day
295                    | Numeric::WeekFromSun
296                    | Numeric::WeekFromMon
297                    | Numeric::IsoWeek
298                    | Numeric::Hour
299                    | Numeric::Hour12
300                    | Numeric::Minute
301                    | Numeric::Second => mask.push_str("99"),
302                    Numeric::NumDaysFromSun | Numeric::WeekdayFromMon => mask.push('9'),
303                    Numeric::Ordinal => mask.push_str("999"),
304                    Numeric::Nanosecond => mask.push_str("999999999"),
305                    Numeric::Timestamp => mask.push_str("###########"),
306                    _ => return Err(fmt::Error),
307                },
308                Item::Numeric(v, Pad::Zero) => match v {
309                    Numeric::Year | Numeric::IsoYear => mask.push_str("0000"),
310                    Numeric::YearDiv100
311                    | Numeric::YearMod100
312                    | Numeric::IsoYearDiv100
313                    | Numeric::IsoYearMod100
314                    | Numeric::Month
315                    | Numeric::Day
316                    | Numeric::WeekFromSun
317                    | Numeric::WeekFromMon
318                    | Numeric::IsoWeek
319                    | Numeric::Hour
320                    | Numeric::Hour12
321                    | Numeric::Minute
322                    | Numeric::Second => mask.push_str("00"),
323                    Numeric::NumDaysFromSun | Numeric::WeekdayFromMon => mask.push('0'),
324                    Numeric::Ordinal => mask.push_str("000"),
325                    Numeric::Nanosecond => mask.push_str("000000000"),
326                    Numeric::Timestamp => mask.push_str("#0000000000"),
327                    _ => return Err(fmt::Error),
328                },
329                Item::Fixed(v) => match v {
330                    Fixed::ShortMonthName => mask.push_str("___"),
331                    Fixed::LongMonthName => mask.push_str("_________"),
332                    Fixed::ShortWeekdayName => mask.push_str("___"),
333                    Fixed::LongWeekdayName => mask.push_str("________"),
334                    Fixed::LowerAmPm => mask.push_str("__"),
335                    Fixed::UpperAmPm => mask.push_str("__"),
336                    Fixed::Nanosecond => mask.push_str(".#########"),
337                    Fixed::Nanosecond3 => mask.push_str(".###"),
338                    Fixed::Nanosecond6 => mask.push_str(".######"),
339                    Fixed::Nanosecond9 => mask.push_str(".#########"),
340                    Fixed::TimezoneName => mask.push_str("__________"),
341                    Fixed::TimezoneOffsetColon | Fixed::TimezoneOffset => mask.push_str("+##:##"),
342                    Fixed::TimezoneOffsetDoubleColon => mask.push_str("+##:##:##"),
343                    Fixed::TimezoneOffsetTripleColon => mask.push_str("+##"),
344                    Fixed::TimezoneOffsetColonZ | Fixed::TimezoneOffsetZ => return Err(fmt::Error),
345                    Fixed::RFC2822 => {
346                        // 01 Jun 2016 14:31:46 -0700
347                        return Err(fmt::Error);
348                    }
349                    Fixed::RFC3339 => {
350                        // not supported, for now
351                        return Err(fmt::Error);
352                    }
353                    _ => return Err(fmt::Error),
354                },
355                Item::Error => return Err(fmt::Error),
356            }
357        }
358
359        self.locale = locale;
360        self.pattern = pattern.as_ref().to_string();
361        self.widget.set_mask(mask)?;
362        Ok(())
363    }
364
365    /// Renders the widget in invalid style.
366    #[inline]
367    pub fn set_invalid(&mut self, invalid: bool) {
368        self.widget.invalid = invalid;
369    }
370
371    /// Renders the widget in invalid style.
372    #[inline]
373    pub fn get_invalid(&self) -> bool {
374        self.widget.invalid
375    }
376
377    /// The next edit operation will overwrite the current content
378    /// instead of adding text. Any move operations will cancel
379    /// this overwrite.
380    #[inline]
381    pub fn set_overwrite(&mut self, overwrite: bool) {
382        self.widget.set_overwrite(overwrite);
383    }
384
385    /// Will the next edit operation overwrite the content?
386    #[inline]
387    pub fn overwrite(&self) -> bool {
388        self.widget.overwrite()
389    }
390}
391
392impl DateInputState {
393    /// Clipboard used.
394    /// Default is to use the [global_clipboard](crate::clipboard::global_clipboard).
395    #[inline]
396    pub fn set_clipboard(&mut self, clip: Option<impl Clipboard + 'static>) {
397        self.widget.set_clipboard(clip);
398    }
399
400    /// Clipboard used.
401    /// Default is to use the [global_clipboard](crate::clipboard::global_clipboard).
402    #[inline]
403    pub fn clipboard(&self) -> Option<&dyn Clipboard> {
404        self.widget.clipboard()
405    }
406
407    /// Copy to clipboard
408    #[inline]
409    pub fn copy_to_clip(&mut self) -> bool {
410        self.widget.copy_to_clip()
411    }
412
413    /// Cut to clipboard
414    #[inline]
415    pub fn cut_to_clip(&mut self) -> bool {
416        self.widget.cut_to_clip()
417    }
418
419    /// Paste from clipboard.
420    #[inline]
421    pub fn paste_from_clip(&mut self) -> bool {
422        self.widget.paste_from_clip()
423    }
424}
425
426impl DateInputState {
427    /// Set undo buffer.
428    #[inline]
429    pub fn set_undo_buffer(&mut self, undo: Option<impl UndoBuffer + 'static>) {
430        self.widget.set_undo_buffer(undo);
431    }
432
433    /// Undo
434    #[inline]
435    pub fn undo_buffer(&self) -> Option<&dyn UndoBuffer> {
436        self.widget.undo_buffer()
437    }
438
439    /// Undo
440    #[inline]
441    pub fn undo_buffer_mut(&mut self) -> Option<&mut dyn UndoBuffer> {
442        self.widget.undo_buffer_mut()
443    }
444
445    /// Get all recent replay recordings.
446    #[inline]
447    pub fn recent_replay_log(&mut self) -> Vec<UndoEntry> {
448        self.widget.recent_replay_log()
449    }
450
451    /// Apply the replay recording.
452    #[inline]
453    pub fn replay_log(&mut self, replay: &[UndoEntry]) {
454        self.widget.replay_log(replay)
455    }
456
457    /// Undo operation
458    #[inline]
459    pub fn undo(&mut self) -> bool {
460        self.widget.undo()
461    }
462
463    /// Redo operation
464    #[inline]
465    pub fn redo(&mut self) -> bool {
466        self.widget.redo()
467    }
468}
469
470impl DateInputState {
471    /// Set and replace all styles.
472    #[inline]
473    pub fn set_styles(&mut self, styles: Vec<(Range<usize>, usize)>) {
474        self.widget.set_styles(styles);
475    }
476
477    /// Add a style for a byte-range.
478    #[inline]
479    pub fn add_style(&mut self, range: Range<usize>, style: usize) {
480        self.widget.add_style(range, style);
481    }
482
483    /// Add a style for a `Range<upos_type>` .
484    /// The style-nr refers to one of the styles set with the widget.
485    #[inline]
486    pub fn add_range_style(
487        &mut self,
488        range: Range<upos_type>,
489        style: usize,
490    ) -> Result<(), TextError> {
491        self.widget.add_range_style(range, style)
492    }
493
494    /// Remove the exact TextRange and style.
495    #[inline]
496    pub fn remove_style(&mut self, range: Range<usize>, style: usize) {
497        self.widget.remove_style(range, style);
498    }
499
500    /// Remove the exact `Range<upos_type>` and style.
501    #[inline]
502    pub fn remove_range_style(
503        &mut self,
504        range: Range<upos_type>,
505        style: usize,
506    ) -> Result<(), TextError> {
507        self.widget.remove_range_style(range, style)
508    }
509
510    /// Find all styles that touch the given range.
511    pub fn styles_in(&self, range: Range<usize>, buf: &mut Vec<(Range<usize>, usize)>) {
512        self.widget.styles_in(range, buf)
513    }
514
515    /// All styles active at the given position.
516    #[inline]
517    pub fn styles_at(&self, byte_pos: usize, buf: &mut Vec<(Range<usize>, usize)>) {
518        self.widget.styles_at(byte_pos, buf)
519    }
520
521    /// Check if the given style applies at the position and
522    /// return the complete range for the style.
523    #[inline]
524    pub fn style_match(&self, byte_pos: usize, style: usize) -> Option<Range<usize>> {
525        self.widget.styles_at_match(byte_pos, style)
526    }
527
528    /// List of all styles.
529    #[inline]
530    pub fn styles(&self) -> Option<impl Iterator<Item = (Range<usize>, usize)> + '_> {
531        self.widget.styles()
532    }
533}
534
535impl DateInputState {
536    /// Offset shown.
537    #[inline]
538    pub fn offset(&self) -> upos_type {
539        self.widget.offset()
540    }
541
542    /// Offset shown. This is corrected if the cursor wouldn't be visible.
543    #[inline]
544    pub fn set_offset(&mut self, offset: upos_type) {
545        self.widget.set_offset(offset)
546    }
547
548    /// Cursor position
549    #[inline]
550    pub fn cursor(&self) -> upos_type {
551        self.widget.cursor()
552    }
553
554    /// Set the cursor position, reset selection.
555    #[inline]
556    pub fn set_cursor(&mut self, cursor: upos_type, extend_selection: bool) -> bool {
557        self.widget.set_cursor(cursor, extend_selection)
558    }
559
560    /// Place cursor at some sensible position according to the mask.
561    #[inline]
562    pub fn set_default_cursor(&mut self) {
563        self.widget.set_default_cursor()
564    }
565
566    /// Selection anchor.
567    #[inline]
568    pub fn anchor(&self) -> upos_type {
569        self.widget.anchor()
570    }
571
572    /// Selection
573    #[inline]
574    pub fn has_selection(&self) -> bool {
575        self.widget.has_selection()
576    }
577
578    /// Selection
579    #[inline]
580    pub fn selection(&self) -> Range<upos_type> {
581        self.widget.selection()
582    }
583
584    /// Selection
585    #[inline]
586    pub fn set_selection(&mut self, anchor: upos_type, cursor: upos_type) -> bool {
587        self.widget.set_selection(anchor, cursor)
588    }
589
590    /// Select all text.
591    #[inline]
592    pub fn select_all(&mut self) {
593        self.widget.select_all();
594    }
595
596    /// Selection
597    #[inline]
598    pub fn selected_text(&self) -> &str {
599        self.widget.selected_text()
600    }
601}
602
603impl DateInputState {
604    /// Empty
605    #[inline]
606    pub fn is_empty(&self) -> bool {
607        self.widget.is_empty()
608    }
609
610    /// Parses the text according to the given pattern.
611    #[inline]
612    pub fn value(&self) -> Result<NaiveDate, chrono::ParseError> {
613        NaiveDate::parse_from_str(self.widget.text(), self.pattern.as_str())
614    }
615
616    /// Length in grapheme count.
617    #[inline]
618    pub fn len(&self) -> upos_type {
619        self.widget.len()
620    }
621
622    /// Length as grapheme count.
623    #[inline]
624    pub fn line_width(&self) -> upos_type {
625        self.widget.line_width()
626    }
627}
628
629impl DateInputState {
630    /// Reset to empty.
631    #[inline]
632    pub fn clear(&mut self) {
633        self.widget.clear();
634    }
635
636    /// Set the date value.
637    #[inline]
638    pub fn set_value(&mut self, date: NaiveDate) {
639        let v = date.format(self.pattern.as_str()).to_string();
640        self.widget.set_text(v);
641    }
642
643    /// Insert a char at the current position.
644    #[inline]
645    pub fn insert_char(&mut self, c: char) -> bool {
646        self.widget.insert_char(c)
647    }
648
649    /// Remove the selected range. The text will be replaced with the default value
650    /// as defined by the mask.
651    #[inline]
652    pub fn delete_range(&mut self, range: Range<upos_type>) -> bool {
653        self.widget.delete_range(range)
654    }
655
656    /// Remove the selected range. The text will be replaced with the default value
657    /// as defined by the mask.
658    #[inline]
659    pub fn try_delete_range(&mut self, range: Range<upos_type>) -> Result<bool, TextError> {
660        self.widget.try_delete_range(range)
661    }
662}
663
664impl DateInputState {
665    /// Delete the char after the cursor.
666    #[inline]
667    pub fn delete_next_char(&mut self) -> bool {
668        self.widget.delete_next_char()
669    }
670
671    /// Delete the char before the cursor.
672    #[inline]
673    pub fn delete_prev_char(&mut self) -> bool {
674        self.widget.delete_prev_char()
675    }
676
677    /// Move to the next char.
678    #[inline]
679    pub fn move_right(&mut self, extend_selection: bool) -> bool {
680        self.widget.move_right(extend_selection)
681    }
682
683    /// Move to the previous char.
684    #[inline]
685    pub fn move_left(&mut self, extend_selection: bool) -> bool {
686        self.widget.move_left(extend_selection)
687    }
688
689    /// Start of line
690    #[inline]
691    pub fn move_to_line_start(&mut self, extend_selection: bool) -> bool {
692        self.widget.move_to_line_start(extend_selection)
693    }
694
695    /// End of line
696    #[inline]
697    pub fn move_to_line_end(&mut self, extend_selection: bool) -> bool {
698        self.widget.move_to_line_end(extend_selection)
699    }
700}
701
702impl HasScreenCursor for DateInputState {
703    /// The current text cursor as an absolute screen position.
704    #[inline]
705    fn screen_cursor(&self) -> Option<(u16, u16)> {
706        self.widget.screen_cursor()
707    }
708}
709
710impl RelocatableState for DateInputState {
711    fn relocate(&mut self, shift: (i16, i16), clip: Rect) {
712        self.area.relocate(shift, clip);
713        self.inner.relocate(shift, clip);
714        self.widget.relocate(shift, clip);
715    }
716}
717
718impl DateInputState {
719    /// Converts a grapheme based position to a screen position
720    /// relative to the widget area.
721    #[inline]
722    pub fn col_to_screen(&self, pos: upos_type) -> Option<u16> {
723        self.widget.col_to_screen(pos)
724    }
725
726    /// Converts from a widget relative screen coordinate to a grapheme index.
727    /// x is the relative screen position.
728    #[inline]
729    pub fn screen_to_col(&self, scx: i16) -> upos_type {
730        self.widget.screen_to_col(scx)
731    }
732
733    /// Set the cursor position from a screen position relative to the origin
734    /// of the widget. This value can be negative, which selects a currently
735    /// not visible position and scrolls to it.
736    #[inline]
737    pub fn set_screen_cursor(&mut self, cursor: i16, extend_selection: bool) -> bool {
738        self.widget.set_screen_cursor(cursor, extend_selection)
739    }
740}
741
742impl HandleEvent<crossterm::event::Event, Regular, TextOutcome> for DateInputState {
743    fn handle(&mut self, event: &crossterm::event::Event, _keymap: Regular) -> TextOutcome {
744        self.widget.handle(event, Regular)
745    }
746}
747
748impl HandleEvent<crossterm::event::Event, ReadOnly, TextOutcome> for DateInputState {
749    fn handle(&mut self, event: &crossterm::event::Event, _keymap: ReadOnly) -> TextOutcome {
750        self.widget.handle(event, ReadOnly)
751    }
752}
753
754impl HandleEvent<crossterm::event::Event, MouseOnly, TextOutcome> for DateInputState {
755    fn handle(&mut self, event: &crossterm::event::Event, _keymap: MouseOnly) -> TextOutcome {
756        self.widget.handle(event, MouseOnly)
757    }
758}
759
760/// Handle all events.
761/// Text events are only processed if focus is true.
762/// Mouse events are processed if they are in range.
763pub fn handle_events(
764    state: &mut DateInputState,
765    focus: bool,
766    event: &crossterm::event::Event,
767) -> TextOutcome {
768    state.widget.focus.set(focus);
769    HandleEvent::handle(state, event, Regular)
770}
771
772/// Handle only navigation events.
773/// Text events are only processed if focus is true.
774/// Mouse events are processed if they are in range.
775pub fn handle_readonly_events(
776    state: &mut DateInputState,
777    focus: bool,
778    event: &crossterm::event::Event,
779) -> TextOutcome {
780    state.widget.focus.set(focus);
781    state.handle(event, ReadOnly)
782}
783
784/// Handle only mouse-events.
785pub fn handle_mouse_events(
786    state: &mut DateInputState,
787    event: &crossterm::event::Event,
788) -> TextOutcome {
789    HandleEvent::handle(state, event, MouseOnly)
790}