1use super::*;
4
5#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
7#[non_exhaustive]
8pub struct DatePickerRenderState {
9 pub is_disabled: bool,
11 pub is_invalid: bool,
13 pub is_read_only: bool,
15 pub is_required: bool,
17 pub is_focus_within: bool,
19 pub is_focus_visible: bool,
21 pub is_open: bool,
23}
24
25#[must_use = "a component does nothing until it is rendered: add it as a child or return it from `render`"]
27#[derive(IntoElement)]
28pub struct DatePicker {
29 locale: Option<SharedString>,
32 content: Option<std::sync::Arc<dyn Fn(DatePickerRenderState) -> gpui::AnyElement + 'static>>,
34 name: Option<SharedString>,
36 default_value: Option<Date>,
38 value: Option<Option<Date>>,
42 constraints: DateConstraints,
43 is_disabled: bool,
44 is_read_only: bool,
45 is_required: bool,
46 validation_behavior: crate::form::ValidationBehavior,
47 validate: Option<crate::validation::Validator<Option<Date>>>,
48 validation_errors: Vec<SharedString>,
49 is_invalid: bool,
50 auto_focus: bool,
51 on_open_change: Option<std::sync::Arc<dyn Fn(&bool, &mut Window, &mut App) + 'static>>,
52 state: Entity<CalendarState>,
53 is_open: Option<bool>,
56 default_open: bool,
57 should_close_on_select: bool,
58 placement: herogpui_core::Placement,
60 label: Option<SharedString>,
61 trigger_indicator: Option<gpui::AnyElement>,
62 on_change: Option<OnChange>,
63 form_state: Rc<RefCell<crate::form::LiveFormFieldState>>,
64 form_is_disabled: Rc<Cell<bool>>,
65 form_default: Rc<RefCell<Option<Date>>>,
66 form_on_change: Rc<RefCell<Option<OnChange>>>,
67 form_field_state: Rc<RefCell<Option<Entity<crate::input::InputState>>>>,
68 sx: Option<Box<gpui::StyleRefinement>>,
70}
71
72impl DatePicker {
73 pub fn locale(mut self, tag: impl Into<SharedString>) -> Self {
78 self.locale = Some(tag.into());
79 self
80 }
81
82 pub fn value(mut self, date: Option<Date>) -> Self {
93 self.value = Some(date);
94 self
95 }
96
97 pub fn min_value(mut self, date: Date) -> Self {
99 self.constraints.min_value = Some(date);
100 self
101 }
102
103 pub fn max_value(mut self, date: Date) -> Self {
105 self.constraints.max_value = Some(date);
106 self
107 }
108
109 pub fn is_date_unavailable(mut self, f: impl Fn(Date) -> bool + 'static) -> Self {
111 self.constraints.is_date_unavailable = Some(std::sync::Arc::new(f));
112 self
113 }
114
115 pub fn first_day_of_week(mut self, day: Weekday) -> Self {
117 self.constraints.first_day_of_week = day;
118 self
119 }
120
121 pub fn constraints(mut self, constraints: DateConstraints) -> Self {
123 self.constraints = constraints;
124 self
125 }
126
127 pub fn is_disabled(mut self, v: bool) -> Self {
129 self.is_disabled = v;
130 self
131 }
132
133 pub fn is_read_only(mut self, v: bool) -> Self {
135 self.is_read_only = v;
136 self
137 }
138
139 pub fn is_required(mut self, v: bool) -> Self {
141 self.is_required = v;
142 self
143 }
144
145 pub fn validation_behavior(mut self, behavior: crate::form::ValidationBehavior) -> Self {
147 self.validation_behavior = behavior;
148 self
149 }
150
151 pub fn validate(mut self, f: impl Fn(&Option<Date>) -> Option<SharedString> + 'static) -> Self {
153 self.validate = Some(std::sync::Arc::new(f));
154 self
155 }
156
157 pub fn validation_errors(
159 mut self,
160 errors: impl IntoIterator<Item = impl Into<SharedString>>,
161 ) -> Self {
162 self.validation_errors = errors.into_iter().map(Into::into).collect();
163 self
164 }
165
166 pub fn is_invalid(mut self, v: bool) -> Self {
168 self.is_invalid = v;
169 self
170 }
171
172 pub fn auto_focus(mut self, v: bool) -> Self {
174 self.auto_focus = v;
175 self
176 }
177
178 pub fn on_open_change(mut self, f: impl Fn(&bool, &mut Window, &mut App) + 'static) -> Self {
180 self.on_open_change = Some(std::sync::Arc::new(f));
181 self
182 }
183
184 pub fn new(state: Entity<CalendarState>) -> Self {
186 let form_state = date_picker_form_state(state.entity_id().as_u64());
187 let form_is_disabled = Rc::new(Cell::new(false));
188 let form_default = Rc::new(RefCell::new(None));
189 let form_on_change: Rc<RefCell<Option<OnChange>>> = Rc::new(RefCell::new(None));
190 let form_field_state = Rc::new(RefCell::new(None::<Entity<crate::input::InputState>>));
191 let restore_form_state = Rc::downgrade(&form_state);
192 let restore_is_disabled = form_is_disabled.clone();
193 let restore_default = form_default.clone();
194 let restore_callback = form_on_change.clone();
195 let restore_state = state.clone();
196 let restore_field_state = form_field_state.clone();
197 #[allow(clippy::arc_with_non_send_sync)]
200 let restore: std::sync::Arc<dyn Fn(&mut Window, &mut App)> =
201 std::sync::Arc::new(move |window, cx| {
202 let date = *restore_default.borrow();
203 restore_state.update(&mut *cx, |state, cx| {
204 state.selected = date;
205 state.selected_dates = date.into_iter().collect();
206 if let Some(date) = date {
207 state.view_year = date.year;
208 state.view_month = date.month;
209 state.view_day = date.day;
210 }
211 cx.notify();
212 });
213 if let Some(field_state) = restore_field_state.borrow().clone() {
214 let text = date.map(|date| date.format_iso()).unwrap_or_default();
215 field_state.update(cx, |state, cx| {
216 state.set_value(text);
217 cx.notify();
218 });
219 }
220 if let Some(state) = restore_form_state.upgrade() {
221 let mut state = state.borrow_mut();
222 state.value = crate::form::FormValue::Text(
223 date.map(|date| date.format_iso())
224 .unwrap_or_default()
225 .into(),
226 );
227 state.is_invalid = false;
228 state.is_successful = !restore_is_disabled.get();
229 }
230 if let Some(callback) = restore_callback.borrow().as_ref() {
231 callback(&date, window, cx);
232 }
233 });
234 form_state.borrow_mut().restore = Some(restore);
235 Self {
236 locale: None,
237 content: None,
238 name: None,
239 default_value: None,
240 value: None,
241 constraints: DateConstraints::new(),
242 is_disabled: false,
243 is_read_only: false,
244 is_required: false,
245 validation_behavior: crate::form::ValidationBehavior::Native,
246 validate: None,
247 validation_errors: Vec::new(),
248 is_invalid: false,
249 auto_focus: false,
250 on_open_change: None,
251 state,
252 is_open: None,
253 default_open: false,
254 should_close_on_select: true,
255 placement: herogpui_core::Placement::Bottom,
256 label: None,
257 trigger_indicator: None,
258 on_change: None,
259 form_state,
260 form_is_disabled,
261 form_default,
262 form_on_change,
263 form_field_state,
264 sx: None,
265 }
266 }
267
268 pub fn name(mut self, name: impl Into<SharedString>) -> Self {
270 self.name = Some(name.into());
271 self
272 }
273
274 pub fn form_field(&self, cx: &App) -> Option<crate::form::FormField> {
279 let name = self.name.clone()?;
280 self.form_is_disabled.set(self.is_disabled);
281 let selected = self.state.read(cx).selected();
282 let text = selected.map(|d| d.format_iso()).unwrap_or_default();
283 if self.form_default.borrow().is_none() {
284 *self.form_default.borrow_mut() = self.state.read(cx).selected;
285 }
286 let mut form_state = self.form_state.borrow_mut();
287 form_state.value = crate::form::FormValue::Text(text.into());
288 form_state.is_successful = !self.is_disabled;
289 let constraint_error = selected
290 .is_some_and(|date| !self.constraints.allows(date))
291 .then(|| SharedString::from("That date is unavailable."));
292 form_state.is_invalid = crate::validation::resolve(
293 self.is_invalid,
294 &self.validation_errors,
295 self.validate
296 .as_ref()
297 .and_then(|validate| validate(&selected)),
298 constraint_error,
299 )
300 .is_invalid;
301 Some(
302 crate::form::FormField::live(name, self.form_state.clone())
303 .is_required(self.is_required)
304 .validation_behavior(self.validation_behavior),
305 )
306 }
307
308 pub fn default_value(mut self, value: Date) -> Self {
313 self.default_value = Some(value);
314 *self.form_default.borrow_mut() = Some(value);
315 self
316 }
317
318 pub fn is_open(mut self, v: bool) -> Self {
320 self.is_open = Some(v);
321 self
322 }
323 pub fn default_open(mut self, v: bool) -> Self {
328 self.default_open = v;
329 self
330 }
331
332 pub fn should_close_on_select(mut self, v: bool) -> Self {
334 self.should_close_on_select = v;
335 self
336 }
337
338 pub fn placement(mut self, placement: herogpui_core::Placement) -> Self {
342 self.placement = placement;
343 self
344 }
345
346 pub fn label(mut self, l: impl Into<SharedString>) -> Self {
348 self.label = Some(l.into());
349 self
350 }
351
352 pub fn trigger_indicator(mut self, indicator: impl IntoElement) -> Self {
354 self.trigger_indicator = Some(indicator.into_any_element());
355 self
356 }
357
358 pub fn content(
360 mut self,
361 render: impl Fn(DatePickerRenderState) -> gpui::AnyElement + 'static,
362 ) -> Self {
363 self.content = Some(std::sync::Arc::new(render));
364 self
365 }
366
367 pub fn on_change(mut self, f: impl Fn(&Option<Date>, &mut Window, &mut App) + 'static) -> Self {
369 let callback = std::sync::Arc::new(f);
370 *self.form_on_change.borrow_mut() = Some(callback.clone());
371 self.on_change = Some(callback);
372 self
373 }
374
375 pub fn sx(mut self, style: impl FnOnce(gpui::Div) -> gpui::Div) -> Self {
380 crate::util::refine_sx(&mut self.sx, style);
381 self
382 }
383}
384
385impl RenderOnce for DatePicker {
386 fn render(self, window: &mut Window, cx: &mut App) -> impl IntoElement {
387 self.form_is_disabled.set(self.is_disabled);
388 let base_id = gpui::ElementId::named_usize("dp", self.state.entity_id().as_u64() as usize);
391 if let Some(date) = self.value {
396 let state = self.state.clone();
397 crate::util::seed_once(
398 window,
399 cx,
400 gpui::ElementId::named_usize(
401 "datepicker-default",
402 self.state.entity_id().as_u64() as usize,
403 ),
404 move |cx| {
405 state.update(cx, |s, cx| {
406 s.selected = date;
407 s.selected_dates = date.into_iter().collect();
408 if let Some(date) = date {
409 s.view_year = date.year;
410 s.view_month = date.month;
411 s.view_day = date.day;
412 }
413 cx.notify();
414 });
415 },
416 );
417 } else if let Some(value) = self.default_value {
418 let state = self.state.clone();
419 crate::util::seed_once(
420 window,
421 cx,
422 gpui::ElementId::named_usize(
423 "datepicker-default",
424 self.state.entity_id().as_u64() as usize,
425 ),
426 move |cx| {
427 state.update(cx, |s, cx| {
428 s.selected = Some(value);
429 s.selected_dates = vec![value];
430 s.view_year = value.year;
431 s.view_month = value.month;
432 s.view_day = value.day;
433 cx.notify();
434 });
435 },
436 );
437 }
438
439 let (is_open, open_own) = crate::util::controlled(
441 window,
442 cx,
443 element_id::scoped(&base_id, "open"),
444 self.is_open,
445 self.default_open,
446 );
447 let open = is_open && !self.is_disabled;
448 let selected = self.state.read(cx).selected;
449 let selected_text = selected.map(|date| date.format_iso()).unwrap_or_default();
450 self.form_state.borrow_mut().value =
451 crate::form::FormValue::Text(selected_text.clone().into());
452 let initial_text = selected_text.clone();
453 let field_state = window
454 .use_keyed_state(
455 element_id::scoped(&base_id, "field-state"),
456 cx,
457 move |_, cx| cx.new(|cx| crate::input::InputState::with_value(cx, initial_text)),
458 )
459 .read(cx)
460 .clone();
461 *self.form_field_state.borrow_mut() = Some(field_state.clone());
462 let field_sync = window.use_keyed_state(element_id::scoped(&base_id, "field-sync"), cx, {
463 let selected_text = selected_text.clone();
464 move |_, _| selected_text
465 });
466 let field_value = field_state.read(cx).value().to_owned();
467 let last_field_value = field_sync.read(cx).clone();
468 let field_follows_selection =
469 field_value == last_field_value && field_value != selected_text;
470 if field_follows_selection {
471 field_state.update(cx, |state, _| state.set_value(selected_text.clone()));
472 }
473 let live_field_value = if field_follows_selection {
474 selected_text.clone()
475 } else {
476 field_value
477 };
478 if live_field_value == selected_text {
479 field_sync.update(cx, |value, _| *value = selected_text.clone());
480 }
481 let (parsed, constraint_error) = if live_field_value.trim().is_empty() {
482 (None, None)
483 } else {
484 let (parsed, _) = parse_value(&live_field_value);
485 let error = match parsed {
486 None => Some(SharedString::from("Enter a valid date.")),
487 Some(date) if !self.constraints.allows(date) => {
488 Some(SharedString::from("That date is unavailable."))
489 }
490 Some(_) => None,
491 };
492 (parsed, error)
493 };
494 let field_invalid = crate::validation::resolve(
495 self.is_invalid,
496 &self.validation_errors,
497 self.validate
498 .as_ref()
499 .and_then(|validate| validate(&parsed)),
500 constraint_error,
501 )
502 .is_invalid;
503 {
504 let mut state = self.form_state.borrow_mut();
505 state.value = crate::form::FormValue::Text(live_field_value.into());
506 state.is_successful = !self.is_disabled;
507 state.is_invalid = field_invalid;
508 }
509 let field_focus = field_state.read(cx).focus_handle(cx);
510 self.form_state.borrow_mut().focus = Some(field_focus.clone());
511 if let Some(render) = self.content.clone() {
512 let content_scope = window
513 .use_keyed_state(
514 element_id::scoped(&base_id, "content-focus"),
515 cx,
516 |_, cx| cx.focus_handle().tab_stop(false),
517 )
518 .read(cx)
519 .clone();
520 let is_focus_within = content_scope.contains_focused(window, cx);
521 let content_root = gpui::div()
522 .relative()
523 .max_w(px(320.))
524 .track_focus(&content_scope)
525 .child(render(DatePickerRenderState {
526 is_disabled: self.is_disabled,
527 is_invalid: field_invalid,
528 is_read_only: self.is_read_only,
529 is_required: self.is_required,
530 is_focus_within,
531 is_focus_visible: is_focus_within && crate::util::focus_visible(cx),
532 is_open: open,
533 }));
534 return crate::util::apply_sx(content_root, &self.sx).into_any_element();
535 }
536 let (overlay_phase, dismissal_token) = crate::util::overlay_scope(
537 window,
538 cx,
539 element_id::scoped(&base_id, "overlay"),
540 open,
541 true,
545 );
546 let panel_visible = overlay_phase != crate::util::OverlayPhase::Closed;
547 let panel_open = overlay_phase == crate::util::OverlayPhase::Open;
548 let requested_placement = window.use_keyed_state(
552 element_id::scoped(&base_id, "requested-placement"),
553 cx,
554 |_, _| self.placement,
555 );
556 let resolved_placement = window.use_keyed_state(
557 element_id::scoped(&base_id, "resolved-placement"),
558 cx,
559 |_, _| Rc::new(Cell::new(None::<crate::popover::PopoverPlacement>)),
560 );
561 if *requested_placement.read(cx) != self.placement {
562 requested_placement.update(cx, |placement, _| *placement = self.placement);
563 resolved_placement.read(cx).set(None);
564 }
565 let resolved_placement = resolved_placement.read(cx).clone();
566 let entry_placement = resolved_placement.get().unwrap_or(self.placement);
567 let blur_open_own = open_own.clone();
568 let blur_open_change = self.on_open_change.clone();
569 let blur_scope =
570 crate::util::close_on_blur(window, cx, &base_id, open, move |window, cx| {
571 if let Some(held) = &blur_open_own {
572 held.update(cx, |value, cx| {
573 *value = false;
574 cx.notify();
575 });
576 }
577 if let Some(callback) = &blur_open_change {
578 callback(&false, window, cx);
579 }
580 });
581 let trigger_focus =
582 crate::util::tab_stop_handle(element_id::scoped(&base_id, "trigger-focus"), window, cx);
583 let initiator =
584 window.use_keyed_state(element_id::scoped(&base_id, "initiator"), cx, |_, _| 0usize);
585 let trigger_pressed = Rc::new(Cell::new(false));
586
587 let open_own_keys = open_own.clone();
588 let open_cb_keys = self.on_open_change.clone();
589 let open_picker = crate::util::shared(move |window: &mut Window, cx: &mut App| {
590 if is_open {
591 return;
592 }
593 if let Some(held) = &open_own_keys {
594 held.update(cx, |open, cx| {
595 *open = true;
596 cx.notify();
597 });
598 }
599 if let Some(cb) = &open_cb_keys {
600 cb(&true, window, cx);
601 }
602 });
603
604 let selected_state = self.state.clone();
605 let user_change = self.on_change.clone();
606 let form_state = self.form_state.clone();
607 let field_forced_invalid = self.is_invalid;
608 let was_open = is_open;
609 let trigger_pressed_for_capture = trigger_pressed.clone();
610 let trigger_open_own = open_own.clone();
611 let trigger_open_cb = self.on_open_change.clone();
612 let trigger_indicator = self.trigger_indicator.unwrap_or_else(|| {
613 gpui::svg()
614 .size(px(16.))
615 .path(icons::CALENDAR)
616 .text_color(cx.colors().muted)
617 .into_any_element()
618 });
619 let mut trigger = gpui::div()
620 .id(element_id::scoped(&base_id, "trigger"))
621 .flex()
622 .items_center()
623 .justify_center()
624 .size(px(24.))
625 .child(
626 gpui::div()
627 .flex()
630 .items_center()
631 .justify_center()
632 .size(px(16.))
633 .child(trigger_indicator),
634 );
635 trigger = crate::util::ring_overlay_if_focused(
638 trigger,
639 &trigger_focus,
640 true,
641 px(0.),
642 Vec::new(),
643 window,
644 cx,
645 );
646 trigger = trigger
650 .a11y_named(
651 a11y::Role::Button,
652 &a11y::Name::labelled(crate::i18n::ui_string(crate::i18n::UiString::Calendar, cx)),
653 )
654 .a11y_expanded(is_open);
655 if !self.is_disabled && !self.is_read_only {
656 let focus_on_press = trigger_focus.clone();
657 let trigger_initiator = initiator.clone();
658 trigger = trigger
659 .capture_any_mouse_down(move |_, _, cx| {
660 trigger_pressed_for_capture.set(true);
661 let pressed = trigger_pressed_for_capture.clone();
662 cx.defer(move |_| pressed.set(false));
663 })
664 .track_focus(&trigger_focus)
665 .cursor(crate::util::interactive_cursor(cx))
666 .on_mouse_down(gpui::MouseButton::Left, move |_, window, cx| {
667 window.focus(&focus_on_press, cx);
668 cx.stop_propagation();
669 })
670 .on_click(move |_, window, cx| {
671 trigger_initiator.update(cx, |part, _| *part = 1);
672 if let Some(held) = &trigger_open_own {
673 held.update(cx, |open, cx| {
674 *open = !was_open;
675 cx.notify();
676 });
677 }
678 if let Some(cb) = &trigger_open_cb {
679 cb(&!was_open, window, cx);
680 }
681 });
682 }
683 if !self.is_disabled && !self.is_read_only {
684 trigger = crate::util::record_focus_bounds(trigger, &trigger_focus, window, cx);
685 }
686 let mut date_field = DateField::new(field_state)
687 .embedded(false)
688 .full_width(true)
689 .is_disabled(self.is_disabled)
690 .is_read_only(self.is_read_only)
691 .is_required(self.is_required)
692 .validation_behavior(self.validation_behavior)
693 .is_invalid(field_invalid)
694 .auto_focus(self.auto_focus)
695 .constraints(self.constraints.clone())
696 .report_invalid_changes()
697 .when_some(self.locale.clone(), |field, tag| field.locale(tag))
698 .suffix(trigger);
699 let open_from_field = open_picker.clone();
700 let field_initiator = initiator.clone();
701 let field_constraints = self.constraints.clone();
702 let field_validate = self.validate.clone();
703 let field_validation_errors = self.validation_errors.clone();
704 date_field = date_field
705 .on_picker_open(move |window, cx| {
706 field_initiator.update(cx, |part, _| *part = 0);
707 open_from_field(window, cx);
708 })
709 .on_change(move |date, window, cx| {
710 let valid_date = date.filter(|date| field_constraints.allows(*date));
711 let constraint_error = (date.is_some() && valid_date.is_none())
712 .then(|| SharedString::from("That date is unavailable."));
713 form_state.borrow_mut().is_invalid = crate::validation::resolve(
714 field_forced_invalid,
715 &field_validation_errors,
716 field_validate
717 .as_ref()
718 .and_then(|validate| validate(&valid_date)),
719 constraint_error,
720 )
721 .is_invalid;
722 selected_state.update(cx, |state, cx| {
723 if date.is_none() || valid_date.is_some() {
724 state.selected = valid_date;
725 state.selected_dates = valid_date.into_iter().collect();
726 }
727 if let Some(date) = valid_date {
728 state.view_year = date.year;
729 state.view_month = date.month;
730 state.view_day = date.day;
731 }
732 cx.notify();
733 });
734 if date.is_none() || valid_date.is_some() {
735 form_state.borrow_mut().value = crate::form::FormValue::Text(
736 valid_date
737 .map(|date| date.format_iso())
738 .unwrap_or_default()
739 .into(),
740 );
741 }
742 if date.is_none() || valid_date.is_some() {
743 if let Some(cb) = &user_change {
744 cb(&valid_date, window, cx);
745 }
746 }
747 });
748
749 let field = gpui::div()
751 .id(base_id.clone())
752 .a11y_named(a11y::Role::Group, &a11y::Name::maybe(self.label.clone()))
753 .w_full()
754 .child(date_field);
755 let anchor_bounds = window
756 .use_keyed_state(element_id::scoped(&base_id, "anchor-bounds"), cx, |_, _| {
757 Rc::new(Cell::new(None::<gpui::Bounds<Pixels>>))
758 })
759 .read(cx)
760 .clone();
761 let field = crate::popover::PopoverTriggerMeasure::new(field, anchor_bounds.clone());
762
763 let mut root = gpui::div().relative().max_w(px(320.));
764 let mut wrapper = gpui::div().flex().flex_col().gap(px(4.)).w_full();
765 if let Some(label) = &self.label {
766 wrapper = wrapper.child(
767 crate::field::Label::new(label.clone())
768 .is_required(self.is_required)
769 .is_disabled(self.is_disabled)
770 .is_invalid(self.is_invalid),
771 );
772 }
773 wrapper = wrapper.child(field);
774 root = root.child(wrapper);
775
776 if panel_visible {
777 let close_own = open_own;
782 let close_cb = self.on_open_change.clone();
783 let restore_field = field_focus;
784 let restore_trigger = trigger_focus;
785 let restore_part = initiator;
786 let close = crate::util::shared(move |window: &mut Window, cx: &mut App| {
787 if let Some(held) = &close_own {
788 held.update(cx, |v, cx| {
789 *v = false;
790 cx.notify();
791 });
792 }
793 if let Some(cb) = &close_cb {
794 cb(&false, window, cx);
795 }
796 if *restore_part.read(cx) == 1 {
797 window.focus(&restore_trigger, cx);
798 } else {
799 window.focus(&restore_field, cx);
800 }
801 });
802
803 let mut cal = Calendar::new(self.state.clone())
804 .constraints(self.constraints.clone())
805 .when_some(self.locale.clone(), |cal, tag| cal.locale(tag))
806 .is_disabled(self.is_disabled)
807 .is_read_only(self.is_read_only)
808 .inert(!panel_open)
809 .autofocus_grid(panel_open)
812 .is_invalid(field_invalid);
813 let pick_close = close.clone();
819 let user_change = self.on_change.clone();
820 let form_state = self.form_state.clone();
821 let calendar_forced_invalid = self.is_invalid;
822 let calendar_validate = self.validate.clone();
823 let calendar_validation_errors = self.validation_errors.clone();
824 let should_close_on_select = self.should_close_on_select;
825 let panel_open_for_change = panel_open;
826 cal = cal.on_change(move |d, window, cx| {
827 if !panel_open_for_change {
828 return;
829 }
830 let mut form_state = form_state.borrow_mut();
831 form_state.value = crate::form::FormValue::Text(
832 d.map(|date| date.format_iso()).unwrap_or_default().into(),
833 );
834 form_state.is_invalid = crate::validation::resolve(
835 calendar_forced_invalid,
836 &calendar_validation_errors,
837 calendar_validate.as_ref().and_then(|validate| validate(d)),
838 None,
839 )
840 .is_invalid;
841 if let Some(cb) = &user_change {
842 cb(d, window, cx);
843 }
844 if should_close_on_select {
845 pick_close(window, cx);
846 }
847 });
848 let esc = close.clone();
849 root = crate::util::dismiss_on_escape_with_token(
850 root,
851 dismissal_token.clone(),
852 move |window, cx| {
853 esc(window, cx);
854 crate::util::DismissResult::Handled
855 },
856 );
857 let outside_close = close.clone();
858 let panel_open_for_dismissal = panel_open;
859 let panel = crate::util::dismiss_on_press_outside_with_token(
860 picker_panel(
861 cx,
862 "date-picker-popover",
863 element_id::scoped(&base_id, "popover"),
864 )
865 .child(cal),
866 dismissal_token,
867 move |window, cx| {
868 if !panel_open_for_dismissal {
869 return crate::util::DismissResult::Declined;
870 }
871 if trigger_pressed.get() {
872 return crate::util::DismissResult::Declined;
873 }
874 outside_close(window, cx);
875 crate::util::DismissResult::Handled
876 },
877 );
878 let (slide_x, slide_y) = placement_entry_offset(entry_placement);
879 let zoom = crate::anim::ZoomBox::panel(px(12.), px(20.)).padding_x(px(12.));
880 let zoom = crate::anim::ZoomBox {
881 slide_x: (slide_x != 0.0).then(|| px(slide_x)),
882 slide_y: (slide_y != 0.0).then(|| px(slide_y)),
883 ..zoom
884 };
885 let panel = if overlay_phase == crate::util::OverlayPhase::Exiting {
886 crate::anim::exiting(
887 panel,
888 element_id::scoped(&base_id, "panel-out"),
889 zoom,
890 crate::anim::Motion::LIST_OUT,
891 cx,
892 )
893 } else {
894 crate::anim::entering_zoom(
895 panel,
896 element_id::scoped(&base_id, "panel"),
897 zoom,
898 crate::anim::Motion::LIST_IN,
899 cx,
900 )
901 };
902 root = root.child(crate::util::floating(
903 crate::popover::popover_with_resolved_placement(
904 anchor_bounds,
905 self.placement,
906 px(PICKER_POPOVER_OFFSET),
907 Some(resolved_placement),
908 panel,
909 ),
910 ));
911 }
912
913 crate::util::apply_sx(root.track_focus(&blur_scope), &self.sx).into_any_element()
914 }
915}
916
917pub(super) fn picker_panel(
923 cx: &App,
924 selector: &'static str,
925 id: gpui::ElementId,
926) -> gpui::Stateful<gpui::Div> {
927 let colors = cx.colors();
928 let layout = cx.layout();
929 gpui::div()
930 .id(id)
931 .p(px(12.))
933 .rounded(px(20.))
935 .max_h_full()
936 .overflow_y_scroll()
937 .restrict_scroll_to_axis()
938 .debug_selector(move || selector.to_owned())
939 .bg(colors.overlay.background)
940 .text_color(colors.overlay.foreground)
941 .when(!layout.overlay_shadow.is_empty(), |e| {
942 e.shadow(layout.overlay_shadow.clone())
943 })
944}
945
946crate::util::impl_component_styled!(DatePicker);