1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
//! Validation guide — min/max [`DatePickerRule`] with Field.
use *;
use component_doc;
/// Constrain selectable dates with [`DatePickerRule`] and surface errors through [`Field`](orbital_core_components::Field).
///
/// Min and max bounds apply on blur and when completing segmented input. Mirror the same
/// bounds on `appearance.min_date` / `appearance.max_date` so the calendar disables out-of-range
/// days before the user commits an invalid value.
///
/// # When to use
///
/// - Booking or scheduling forms with policy date windows
/// - Admin tools that must reject dates outside a fiscal or contract period
/// - Any labeled field where validation messages should appear below the control
///
/// # Usage
///
/// 1. Define min and max as `Signal<Option<OrbitalDateTime>>`.
/// 2. Attach rules on [`DatePickerBind`](orbital_core_components::DatePickerBind): `DatePickerRule::min_date(min)` and `DatePickerRule::max_date(max)`.
/// 3. Pass the same signals to `appearance.min_date` and `appearance.max_date` on the picker.
/// 4. Wrap the picker in [`Field`] with a stable `name` for accessible error association.
///
/// # Best Practices
///
/// ## Do's
///
/// * Keep bind rules and appearance bounds in sync so keyboard entry and calendar clicks behave the same
/// * Use UTC or a fixed timezone for policy bounds that must not drift with DST
/// * Show the allowed range in helper text when users frequently hit validation errors
///
/// ## Don'ts
///
/// * Do not validate only on submit — picker rules run on blur for immediate feedback
/// * Do not set min/max on appearance without matching bind rules (or vice versa)
///
/// # Validation reference
///
/// | API | Description |
/// |-----|-------------|
/// | [`DatePickerRule::min_date`] | Rejects values before the bound day |
/// | [`DatePickerRule::max_date`] | Rejects values after the bound day |
/// | `appearance.min_date` / `max_date` | Disables calendar days outside the range |
///
/// # Examples
///
/// ## Out of range
/// Allowed range is **01/01/2025** through **01/01/2026** (US date mask, UTC bounds). Type a date outside that window — for example **01/01/2020** or **01/02/2027** — then tab away; the [`Field`](orbital_core_components::Field) shows a validation error on blur.
/// <!-- preview -->
/// ```rust
/// use orbital_core_components::{DatePicker, DatePickerAppearance, DatePickerBind, Field};
/// use orbital_base_components::{DatePickerRule, DatetimeTimezone, OrbitalDateTime, TryFromUnixSeconds};
/// use crate::preview::{PickerPreviewExample, PickerPreviewKnobs};
/// let value = RwSignal::new(None::<OrbitalDateTime>);
/// let min = Signal::from(Some(
/// OrbitalDateTime::try_from_unix_seconds(1735689600_i64, DatetimeTimezone::Utc)
/// .expect("valid min"),
/// ));
/// let max = Signal::from(Some(
/// OrbitalDateTime::try_from_unix_seconds(1767225600_i64, DatetimeTimezone::Utc)
/// .expect("valid max"),
/// ));
/// view! {
/// <PickerPreviewExample data_testid="date-pickers-validation-preview">
/// <PickerPreviewKnobs />
/// <Field label="Event date" name="event_date">
/// <DatePicker
/// bind=DatePickerBind {
/// value: value.into(),
/// rules: vec![DatePickerRule::min_date(min), DatePickerRule::max_date(max)],
/// ..Default::default()
/// }
/// appearance=DatePickerAppearance { min_date: min, max_date: max, ..Default::default() }
/// />
/// </Field>
/// </PickerPreviewExample>
/// }
/// ```