Skip to main content

fret_ui_kit/window_overlays/
requests.rs

1use std::sync::Arc;
2
3use fret_core::Px;
4use fret_runtime::Model;
5use fret_ui::action::{
6    OnCloseAutoFocus, OnDismissRequest, OnDismissiblePointerMove, OnOpenAutoFocus,
7};
8use fret_ui::element::AnyElement;
9use fret_ui::elements::GlobalElementId;
10
11use super::{
12    DEFAULT_VISIBLE_TOASTS, ToastPosition, ToastStore, ToastVariant, toast_layer_root_name,
13};
14
15#[derive(Debug, Clone)]
16pub enum ToastIconOverride {
17    /// Do not render an icon.
18    Hidden,
19    /// Render a text glyph (e.g. "!" / "i" / "×").
20    Glyph(Arc<str>),
21    /// Render an SVG icon from the shared icon registry (`fret-icons`).
22    #[cfg(feature = "icons")]
23    IconId(fret_icons::IconId),
24}
25
26impl ToastIconOverride {
27    pub fn hidden() -> Self {
28        Self::Hidden
29    }
30
31    pub fn glyph(glyph: impl Into<Arc<str>>) -> Self {
32        Self::Glyph(glyph.into())
33    }
34
35    #[cfg(feature = "icons")]
36    pub fn icon(icon: fret_icons::IconId) -> Self {
37        Self::IconId(icon)
38    }
39}
40
41#[derive(Debug, Default, Clone)]
42pub struct ToastIconOverrides {
43    /// Overrides the toast close button icon (`icons.close` in Sonner).
44    pub close_button: Option<ToastIconOverride>,
45    /// Overrides the loading icon (`icons.loading` in Sonner).
46    pub loading: Option<ToastIconOverride>,
47    /// Overrides the success icon (`icons.success` in Sonner).
48    pub success: Option<ToastIconOverride>,
49    /// Overrides the info icon (`icons.info` in Sonner).
50    pub info: Option<ToastIconOverride>,
51    /// Overrides the warning icon (`icons.warning` in Sonner).
52    pub warning: Option<ToastIconOverride>,
53    /// Overrides the error icon (`icons.error` in Sonner).
54    ///
55    /// In Fret this applies to both `ToastVariant::Error` and `ToastVariant::Destructive`.
56    pub error: Option<ToastIconOverride>,
57}
58
59impl ToastIconOverrides {
60    pub fn for_variant(&self, variant: ToastVariant) -> Option<&ToastIconOverride> {
61        match variant {
62            ToastVariant::Success => self.success.as_ref(),
63            ToastVariant::Info => self.info.as_ref(),
64            ToastVariant::Warning => self.warning.as_ref(),
65            ToastVariant::Error | ToastVariant::Destructive => self.error.as_ref(),
66            ToastVariant::Loading | ToastVariant::Default => None,
67        }
68    }
69}
70
71/// Sonner-style viewport offsets (`offset` / `mobileOffset`).
72///
73/// Sonner allows specifying either a single value or per-side offsets. In Fret we model the
74/// per-side shape directly and apply default fallbacks at render time.
75#[derive(Debug, Default, Clone, Copy, PartialEq)]
76pub struct ToastOffset {
77    pub top: Option<Px>,
78    pub right: Option<Px>,
79    pub bottom: Option<Px>,
80    pub left: Option<Px>,
81}
82
83impl ToastOffset {
84    pub fn all(px: Px) -> Self {
85        Self {
86            top: Some(px),
87            right: Some(px),
88            bottom: Some(px),
89            left: Some(px),
90        }
91    }
92
93    pub fn top(mut self, px: Px) -> Self {
94        self.top = Some(px);
95        self
96    }
97
98    pub fn right(mut self, px: Px) -> Self {
99        self.right = Some(px);
100        self
101    }
102
103    pub fn bottom(mut self, px: Px) -> Self {
104        self.bottom = Some(px);
105        self
106    }
107
108    pub fn left(mut self, px: Px) -> Self {
109        self.left = Some(px);
110        self
111    }
112}
113
114#[derive(Debug, Clone)]
115pub struct ToastVariantColors {
116    pub bg: String,
117    pub fg: String,
118}
119
120impl ToastVariantColors {
121    pub fn new(bg: impl Into<String>, fg: impl Into<String>) -> Self {
122        Self {
123            bg: bg.into(),
124            fg: fg.into(),
125        }
126    }
127}
128
129#[derive(Debug, Clone)]
130pub struct ToastVariantPalette {
131    pub default: ToastVariantColors,
132    pub destructive: ToastVariantColors,
133    pub success: ToastVariantColors,
134    pub info: ToastVariantColors,
135    pub warning: ToastVariantColors,
136    pub error: ToastVariantColors,
137    pub loading: ToastVariantColors,
138}
139
140impl Default for ToastVariantPalette {
141    fn default() -> Self {
142        // Default keys match the shadcn/sonner theme surface. These are used as the baseline for
143        // `fret-ui-kit`'s toast layer renderer.
144        Self {
145            default: ToastVariantColors::new("popover", "popover-foreground"),
146            destructive: ToastVariantColors::new("destructive", "destructive-foreground"),
147            success: ToastVariantColors::new("success", "success-foreground"),
148            info: ToastVariantColors::new("info", "info-foreground"),
149            warning: ToastVariantColors::new("warning", "warning-foreground"),
150            error: ToastVariantColors::new("destructive", "destructive-foreground"),
151            loading: ToastVariantColors::new("popover", "popover-foreground"),
152        }
153    }
154}
155
156impl ToastVariantPalette {
157    pub fn for_variant(&self, variant: ToastVariant) -> &ToastVariantColors {
158        match variant {
159            ToastVariant::Default => &self.default,
160            ToastVariant::Destructive => &self.destructive,
161            ToastVariant::Success => &self.success,
162            ToastVariant::Info => &self.info,
163            ToastVariant::Warning => &self.warning,
164            ToastVariant::Error => &self.error,
165            ToastVariant::Loading => &self.loading,
166        }
167    }
168}
169
170#[derive(Debug, Clone, Default)]
171pub struct ToastTextStyle {
172    pub style_key: Option<String>,
173    pub color_key: Option<String>,
174}
175
176#[derive(Debug, Clone)]
177pub struct ToastButtonStyle {
178    pub label_style_key: Option<String>,
179    pub label_color_key: Option<String>,
180    pub state_layer_color_key: Option<String>,
181    pub hover_state_layer_opacity_key: Option<String>,
182    pub focus_state_layer_opacity_key: Option<String>,
183    pub pressed_state_layer_opacity_key: Option<String>,
184    pub hover_state_layer_opacity: f32,
185    pub focus_state_layer_opacity: f32,
186    pub pressed_state_layer_opacity: f32,
187    pub padding: fret_core::Edges,
188    pub radius: fret_core::Px,
189}
190
191impl Default for ToastButtonStyle {
192    fn default() -> Self {
193        Self {
194            label_style_key: None,
195            label_color_key: None,
196            state_layer_color_key: Some("muted".to_string()),
197            hover_state_layer_opacity_key: None,
198            focus_state_layer_opacity_key: None,
199            pressed_state_layer_opacity_key: None,
200            hover_state_layer_opacity: 0.6,
201            focus_state_layer_opacity: 0.6,
202            pressed_state_layer_opacity: 0.8,
203            padding: fret_core::Edges {
204                left: Px(8.0),
205                right: Px(8.0),
206                top: Px(4.0),
207                bottom: Px(4.0),
208            },
209            radius: Px(6.0),
210        }
211    }
212}
213
214#[derive(Debug, Clone)]
215pub struct ToastIconButtonStyle {
216    pub icon_color_key: Option<String>,
217    pub state_layer_color_key: Option<String>,
218    pub hover_state_layer_opacity_key: Option<String>,
219    pub focus_state_layer_opacity_key: Option<String>,
220    pub pressed_state_layer_opacity_key: Option<String>,
221    pub hover_state_layer_opacity: f32,
222    pub focus_state_layer_opacity: f32,
223    pub pressed_state_layer_opacity: f32,
224    pub padding: fret_core::Edges,
225    pub radius: fret_core::Px,
226}
227
228impl Default for ToastIconButtonStyle {
229    fn default() -> Self {
230        Self {
231            icon_color_key: None,
232            state_layer_color_key: Some("muted".to_string()),
233            hover_state_layer_opacity_key: None,
234            focus_state_layer_opacity_key: None,
235            pressed_state_layer_opacity_key: None,
236            hover_state_layer_opacity: 0.6,
237            focus_state_layer_opacity: 0.6,
238            pressed_state_layer_opacity: 0.8,
239            padding: fret_core::Edges {
240                left: Px(8.0),
241                right: Px(8.0),
242                top: Px(4.0),
243                bottom: Px(4.0),
244            },
245            radius: Px(6.0),
246        }
247    }
248}
249
250#[derive(Debug, Clone)]
251pub struct ToastLayerStyle {
252    pub palette: ToastVariantPalette,
253    /// Optional shadow for the toast container.
254    pub shadow: Option<fret_ui::element::ShadowStyle>,
255    /// Sonner-style icon overrides (`icons.*`).
256    pub icons: ToastIconOverrides,
257    /// Whether to render a close (X) icon button on toasts.
258    ///
259    /// Note: this is distinct from per-toast "dismissible" behavior (e.g. swipe-to-dismiss).
260    pub show_close_button: bool,
261    /// A11y label for the close button (Sonner: `closeButtonAriaLabel`, default: "Close toast").
262    pub close_button_aria_label: Option<Arc<str>>,
263    /// Motion timing for enter/exit presence.
264    ///
265    /// Defaults keep the existing shadcn-aligned behavior.
266    pub open_ticks: u64,
267    pub close_ticks: u64,
268    pub easing: Option<fret_ui::theme::CubicBezier>,
269    pub slide_distance: Px,
270    /// Optional border color. When omitted, no border is drawn.
271    pub border_color_key: Option<String>,
272    pub border_width: Px,
273    pub description_color_key: Option<String>,
274    pub icon_size: Px,
275    pub single_line_min_height: Option<Px>,
276    pub two_line_min_height: Option<Px>,
277    pub container_padding: Option<fret_core::Edges>,
278    pub container_radius: Option<fret_core::Px>,
279    pub title: ToastTextStyle,
280    pub description: ToastTextStyle,
281    pub action: ToastButtonStyle,
282    pub cancel: ToastButtonStyle,
283    pub close: ToastIconButtonStyle,
284}
285
286impl Default for ToastLayerStyle {
287    fn default() -> Self {
288        Self {
289            palette: ToastVariantPalette::default(),
290            shadow: None,
291            icons: ToastIconOverrides::default(),
292            show_close_button: true,
293            close_button_aria_label: Some(Arc::from("Close toast")),
294            open_ticks: 12,
295            close_ticks: 12,
296            easing: None,
297            slide_distance: Px(16.0),
298            border_color_key: Some("border".to_string()),
299            border_width: Px(1.0),
300            description_color_key: Some("muted-foreground".to_string()),
301            icon_size: Px(16.0),
302            single_line_min_height: None,
303            two_line_min_height: None,
304            container_padding: None,
305            container_radius: None,
306            title: ToastTextStyle::default(),
307            description: ToastTextStyle::default(),
308            action: ToastButtonStyle::default(),
309            cancel: ToastButtonStyle::default(),
310            close: ToastIconButtonStyle::default(),
311        }
312    }
313}
314
315pub struct DismissiblePopoverRequest {
316    pub id: GlobalElementId,
317    pub root_name: String,
318    pub trigger: GlobalElementId,
319    /// Extra subtrees that should be treated as "inside" for outside-press + focus-outside
320    /// dismissal policy (Radix DismissableLayer branches).
321    pub dismissable_branches: Vec<GlobalElementId>,
322    pub consume_outside_pointer_events: bool,
323    /// When true and the popover is open, pointer events outside the overlay subtree should not
324    /// reach underlay widgets (Radix `disableOutsidePointerEvents` outcome).
325    pub disable_outside_pointer_events: bool,
326    pub close_on_window_focus_lost: bool,
327    pub close_on_window_resize: bool,
328    pub open: Model<bool>,
329    pub present: bool,
330    pub initial_focus: Option<GlobalElementId>,
331    pub on_open_auto_focus: Option<OnOpenAutoFocus>,
332    pub on_close_auto_focus: Option<OnCloseAutoFocus>,
333    pub on_dismiss_request: Option<OnDismissRequest>,
334    pub on_pointer_move: Option<OnDismissiblePointerMove>,
335    pub children: Vec<AnyElement>,
336}
337
338#[derive(Clone)]
339pub(super) struct CachedDismissiblePopoverDecl {
340    pub owner: Option<GlobalElementId>,
341    pub id: GlobalElementId,
342    pub root_name: String,
343    pub trigger: GlobalElementId,
344    pub dismissable_branches: Vec<GlobalElementId>,
345    pub consume_outside_pointer_events: bool,
346    pub disable_outside_pointer_events: bool,
347    pub close_on_window_focus_lost: bool,
348    pub close_on_window_resize: bool,
349    pub open: Model<bool>,
350    pub initial_focus: Option<GlobalElementId>,
351    pub on_open_auto_focus: Option<OnOpenAutoFocus>,
352    pub on_close_auto_focus: Option<OnCloseAutoFocus>,
353    pub on_dismiss_request: Option<OnDismissRequest>,
354    pub on_pointer_move: Option<OnDismissiblePointerMove>,
355}
356
357impl CachedDismissiblePopoverDecl {
358    pub(super) fn from_request(
359        req: &DismissiblePopoverRequest,
360        owner: Option<GlobalElementId>,
361    ) -> Self {
362        Self {
363            owner,
364            id: req.id,
365            root_name: req.root_name.clone(),
366            trigger: req.trigger,
367            dismissable_branches: req.dismissable_branches.clone(),
368            consume_outside_pointer_events: req.consume_outside_pointer_events,
369            disable_outside_pointer_events: req.disable_outside_pointer_events,
370            close_on_window_focus_lost: req.close_on_window_focus_lost,
371            close_on_window_resize: req.close_on_window_resize,
372            open: req.open.clone(),
373            initial_focus: req.initial_focus,
374            on_open_auto_focus: req.on_open_auto_focus.clone(),
375            on_close_auto_focus: req.on_close_auto_focus.clone(),
376            on_dismiss_request: req.on_dismiss_request.clone(),
377            on_pointer_move: req.on_pointer_move.clone(),
378        }
379    }
380}
381
382impl std::fmt::Debug for DismissiblePopoverRequest {
383    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
384        f.debug_struct("DismissiblePopoverRequest")
385            .field("id", &self.id)
386            .field("root_name", &self.root_name)
387            .field("trigger", &self.trigger)
388            .field("dismissable_branches_len", &self.dismissable_branches.len())
389            .field(
390                "consume_outside_pointer_events",
391                &self.consume_outside_pointer_events,
392            )
393            .field(
394                "disable_outside_pointer_events",
395                &self.disable_outside_pointer_events,
396            )
397            .field(
398                "close_on_window_focus_lost",
399                &self.close_on_window_focus_lost,
400            )
401            .field("close_on_window_resize", &self.close_on_window_resize)
402            .field("open", &"<model>")
403            .field("present", &self.present)
404            .field("initial_focus", &self.initial_focus)
405            .field("on_open_auto_focus", &self.on_open_auto_focus.is_some())
406            .field("on_close_auto_focus", &self.on_close_auto_focus.is_some())
407            .field("on_dismiss_request", &self.on_dismiss_request.is_some())
408            .field("on_pointer_move", &self.on_pointer_move.is_some())
409            .field("children_len", &self.children.len())
410            .finish()
411    }
412}
413
414pub struct ModalRequest {
415    pub id: GlobalElementId,
416    pub root_name: String,
417    pub trigger: Option<GlobalElementId>,
418    pub close_on_window_focus_lost: bool,
419    pub close_on_window_resize: bool,
420    pub open: Model<bool>,
421    pub present: bool,
422    pub initial_focus: Option<GlobalElementId>,
423    pub on_open_auto_focus: Option<OnOpenAutoFocus>,
424    pub on_close_auto_focus: Option<OnCloseAutoFocus>,
425    pub on_dismiss_request: Option<OnDismissRequest>,
426    pub children: Vec<AnyElement>,
427}
428
429#[derive(Clone)]
430pub(super) struct CachedModalDecl {
431    pub owner: Option<GlobalElementId>,
432    pub id: GlobalElementId,
433    pub root_name: String,
434    pub trigger: Option<GlobalElementId>,
435    pub close_on_window_focus_lost: bool,
436    pub close_on_window_resize: bool,
437    pub open: Model<bool>,
438    pub initial_focus: Option<GlobalElementId>,
439    pub on_open_auto_focus: Option<OnOpenAutoFocus>,
440    pub on_close_auto_focus: Option<OnCloseAutoFocus>,
441    pub on_dismiss_request: Option<OnDismissRequest>,
442}
443
444impl CachedModalDecl {
445    pub(super) fn from_request(req: &ModalRequest, owner: Option<GlobalElementId>) -> Self {
446        Self {
447            owner,
448            id: req.id,
449            root_name: req.root_name.clone(),
450            trigger: req.trigger,
451            close_on_window_focus_lost: req.close_on_window_focus_lost,
452            close_on_window_resize: req.close_on_window_resize,
453            open: req.open.clone(),
454            initial_focus: req.initial_focus,
455            on_open_auto_focus: req.on_open_auto_focus.clone(),
456            on_close_auto_focus: req.on_close_auto_focus.clone(),
457            on_dismiss_request: req.on_dismiss_request.clone(),
458        }
459    }
460}
461
462impl std::fmt::Debug for ModalRequest {
463    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
464        f.debug_struct("ModalRequest")
465            .field("id", &self.id)
466            .field("root_name", &self.root_name)
467            .field("trigger", &self.trigger)
468            .field(
469                "close_on_window_focus_lost",
470                &self.close_on_window_focus_lost,
471            )
472            .field("close_on_window_resize", &self.close_on_window_resize)
473            .field("open", &"<model>")
474            .field("present", &self.present)
475            .field("initial_focus", &self.initial_focus)
476            .field("on_open_auto_focus", &self.on_open_auto_focus.is_some())
477            .field("on_close_auto_focus", &self.on_close_auto_focus.is_some())
478            .field("on_dismiss_request", &self.on_dismiss_request.is_some())
479            .field("children_len", &self.children.len())
480            .finish()
481    }
482}
483
484pub struct HoverOverlayRequest {
485    pub id: GlobalElementId,
486    pub root_name: String,
487    /// Whether the overlay should participate in hit-testing and input routing.
488    ///
489    /// When `false`, the overlay may remain mounted for close transitions but must be click-through
490    /// and excluded from any observer passes.
491    pub interactive: bool,
492    pub trigger: GlobalElementId,
493    pub open: Model<bool>,
494    pub present: bool,
495    pub on_pointer_move: Option<OnDismissiblePointerMove>,
496    pub children: Vec<AnyElement>,
497}
498
499#[derive(Clone)]
500pub(super) struct CachedHoverOverlayDecl {
501    pub owner: Option<GlobalElementId>,
502    pub id: GlobalElementId,
503    pub root_name: String,
504    pub interactive: bool,
505    pub trigger: GlobalElementId,
506    pub open: Model<bool>,
507}
508
509impl CachedHoverOverlayDecl {
510    pub(super) fn from_request(req: &HoverOverlayRequest, owner: Option<GlobalElementId>) -> Self {
511        Self {
512            owner,
513            id: req.id,
514            root_name: req.root_name.clone(),
515            interactive: req.interactive,
516            trigger: req.trigger,
517            open: req.open.clone(),
518        }
519    }
520}
521
522impl std::fmt::Debug for HoverOverlayRequest {
523    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
524        f.debug_struct("HoverOverlayRequest")
525            .field("id", &self.id)
526            .field("root_name", &self.root_name)
527            .field("interactive", &self.interactive)
528            .field("trigger", &self.trigger)
529            .field("open", &"<model>")
530            .field("present", &self.present)
531            .field("on_pointer_move", &self.on_pointer_move.is_some())
532            .field("children_len", &self.children.len())
533            .finish()
534    }
535}
536
537pub struct TooltipRequest {
538    pub id: GlobalElementId,
539    pub root_name: String,
540    /// Whether the tooltip should participate in input observer routing.
541    ///
542    /// When `false`, the tooltip may remain mounted for close transitions but must not observe
543    /// outside-press or pointer-move events.
544    pub interactive: bool,
545    pub trigger: Option<GlobalElementId>,
546    pub open: Model<bool>,
547    pub present: bool,
548    pub on_dismiss_request: Option<OnDismissRequest>,
549    pub on_pointer_move: Option<OnDismissiblePointerMove>,
550    pub children: Vec<AnyElement>,
551}
552
553#[derive(Clone)]
554pub(super) struct CachedTooltipDecl {
555    pub owner: Option<GlobalElementId>,
556    pub id: GlobalElementId,
557    pub root_name: String,
558    pub interactive: bool,
559    pub trigger: Option<GlobalElementId>,
560    pub open: Model<bool>,
561    pub on_dismiss_request: Option<OnDismissRequest>,
562    pub on_pointer_move: Option<OnDismissiblePointerMove>,
563}
564
565impl CachedTooltipDecl {
566    pub(super) fn from_request(req: &TooltipRequest, owner: Option<GlobalElementId>) -> Self {
567        Self {
568            owner,
569            id: req.id,
570            root_name: req.root_name.clone(),
571            interactive: req.interactive,
572            trigger: req.trigger,
573            open: req.open.clone(),
574            on_dismiss_request: req.on_dismiss_request.clone(),
575            on_pointer_move: req.on_pointer_move.clone(),
576        }
577    }
578}
579
580#[derive(Clone)]
581pub(super) struct CachedToastLayerDecl {
582    pub owner: Option<GlobalElementId>,
583    pub request: ToastLayerRequest,
584}
585
586impl CachedToastLayerDecl {
587    pub(super) fn from_request(req: &ToastLayerRequest, owner: Option<GlobalElementId>) -> Self {
588        Self {
589            owner,
590            request: req.clone(),
591        }
592    }
593}
594
595impl std::fmt::Debug for TooltipRequest {
596    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
597        f.debug_struct("TooltipRequest")
598            .field("id", &self.id)
599            .field("root_name", &self.root_name)
600            .field("interactive", &self.interactive)
601            .field("trigger", &self.trigger)
602            .field("open", &"<model>")
603            .field("present", &self.present)
604            .field("on_dismiss_request", &self.on_dismiss_request.is_some())
605            .field("on_pointer_move", &self.on_pointer_move.is_some())
606            .field("children_len", &self.children.len())
607            .finish()
608    }
609}
610
611#[derive(Clone)]
612pub struct ToastLayerRequest {
613    pub id: GlobalElementId,
614    pub root_name: String,
615    pub store: Model<ToastStore>,
616    pub position: ToastPosition,
617    pub style: ToastLayerStyle,
618    pub toaster_id: Option<Arc<str>>,
619    pub visible_toasts: usize,
620    pub expand_by_default: bool,
621    pub rich_colors: bool,
622    pub invert: bool,
623    pub container_aria_label: Option<Arc<str>>,
624    pub custom_aria_label: Option<Arc<str>>,
625    pub offset: Option<ToastOffset>,
626    pub mobile_offset: Option<ToastOffset>,
627    pub margin: Option<Px>,
628    pub gap: Option<Px>,
629    pub toast_min_width: Option<Px>,
630    pub toast_max_width: Option<Px>,
631}
632
633impl std::fmt::Debug for ToastLayerRequest {
634    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
635        f.debug_struct("ToastLayerRequest")
636            .field("id", &self.id)
637            .field("root_name", &self.root_name)
638            .field("store", &"<model>")
639            .field("position", &self.position)
640            .field("style", &self.style)
641            .field("toaster_id", &self.toaster_id)
642            .field("visible_toasts", &self.visible_toasts)
643            .field("expand_by_default", &self.expand_by_default)
644            .field("rich_colors", &self.rich_colors)
645            .field("invert", &self.invert)
646            .field("container_aria_label", &self.container_aria_label)
647            .field("custom_aria_label", &self.custom_aria_label)
648            .field("offset", &self.offset)
649            .field("mobile_offset", &self.mobile_offset)
650            .field("margin", &self.margin)
651            .field("gap", &self.gap)
652            .field("toast_min_width", &self.toast_min_width)
653            .field("toast_max_width", &self.toast_max_width)
654            .finish()
655    }
656}
657
658impl ToastLayerRequest {
659    pub fn new(id: GlobalElementId, store: Model<ToastStore>) -> Self {
660        Self {
661            id,
662            root_name: toast_layer_root_name(id),
663            store,
664            position: ToastPosition::default(),
665            style: ToastLayerStyle::default(),
666            toaster_id: None,
667            visible_toasts: DEFAULT_VISIBLE_TOASTS,
668            expand_by_default: false,
669            rich_colors: false,
670            invert: false,
671            container_aria_label: None,
672            custom_aria_label: None,
673            offset: None,
674            mobile_offset: None,
675            margin: None,
676            gap: None,
677            toast_min_width: None,
678            toast_max_width: None,
679        }
680    }
681
682    pub fn position(mut self, position: ToastPosition) -> Self {
683        self.position = position;
684        self
685    }
686
687    pub fn root_name(mut self, root_name: impl Into<String>) -> Self {
688        self.root_name = root_name.into();
689        self
690    }
691
692    pub fn style(mut self, style: ToastLayerStyle) -> Self {
693        self.style = style;
694        self
695    }
696
697    pub fn toaster_id_opt(mut self, id: Option<Arc<str>>) -> Self {
698        self.toaster_id = id;
699        self
700    }
701
702    pub fn visible_toasts(mut self, visible_toasts: usize) -> Self {
703        self.visible_toasts = visible_toasts.max(1);
704        self
705    }
706
707    pub fn expand_by_default(mut self, expand: bool) -> Self {
708        self.expand_by_default = expand;
709        self
710    }
711
712    pub fn rich_colors(mut self, rich_colors: bool) -> Self {
713        self.rich_colors = rich_colors;
714        self
715    }
716
717    pub fn invert(mut self, invert: bool) -> Self {
718        self.invert = invert;
719        self
720    }
721
722    /// Sets the a11y label for the toast viewport container (mirrors Sonner `containerAriaLabel`).
723    pub fn container_aria_label(mut self, label: impl Into<Arc<str>>) -> Self {
724        self.container_aria_label = Some(label.into());
725        self
726    }
727
728    pub fn container_aria_label_opt(mut self, label: Option<Arc<str>>) -> Self {
729        match label {
730            Some(label) => self.container_aria_label(label),
731            None => {
732                self.container_aria_label = None;
733                self
734            }
735        }
736    }
737
738    /// Sets a custom a11y label for the toast viewport container (mirrors Sonner `customAriaLabel`).
739    pub fn custom_aria_label_opt(mut self, label: Option<Arc<str>>) -> Self {
740        self.custom_aria_label = label;
741        self
742    }
743
744    pub fn offset(mut self, offset: ToastOffset) -> Self {
745        self.offset = Some(offset);
746        self
747    }
748
749    pub fn mobile_offset(mut self, offset: ToastOffset) -> Self {
750        self.mobile_offset = Some(offset);
751        self
752    }
753
754    pub fn margin(mut self, margin: Px) -> Self {
755        self.margin = Some(margin);
756        self
757    }
758
759    pub fn gap(mut self, gap: Px) -> Self {
760        self.gap = Some(gap);
761        self
762    }
763
764    pub fn toast_min_width(mut self, width: Px) -> Self {
765        self.toast_min_width = Some(width);
766        self
767    }
768
769    pub fn toast_max_width(mut self, width: Px) -> Self {
770        self.toast_max_width = Some(width);
771        self
772    }
773}