yew_full_calendar/options/
mod.rs

1#[derive(Debug, Clone, serde::Serialize, PartialEq, Eq, Default)]
2#[serde(rename_all = "camelCase")]
3pub enum Header {
4    #[default]
5    Title,
6    Previous,
7    Next,
8    Today,
9    PreviousYear,
10    NextYear,
11}
12impl AsRef<str> for Header {
13    fn as_ref(&self) -> &str {
14        match self {
15            Self::Title => "title",
16            Self::Previous => "prev",
17            Self::Next => "next",
18            Self::Today => "today",
19            Self::PreviousYear => "prevYear",
20            Self::NextYear => "nextYear",
21        }
22    }
23}
24
25#[derive(Debug, Clone, serde::Serialize, PartialEq, Eq)]
26pub struct HeaderSections {
27    pub left: String,
28    pub center: String,
29    pub right: String,
30}
31
32impl Default for HeaderSections {
33    fn default() -> Self {
34        Self {
35            left: Header::Title.as_ref().to_string(),
36            center: String::new(),
37            right: format!(
38                "{} {},{}",
39                Header::Today.as_ref(),
40                Header::Previous.as_ref(),
41                Header::Next.as_ref()
42            ),
43        }
44    }
45}
46
47#[derive(Debug, Clone, serde::Serialize, PartialEq, Eq)]
48#[serde(untagged)]
49pub enum HeaderOptions {
50    Active(bool),
51    Options(HeaderSections),
52}
53
54impl Default for HeaderOptions {
55    fn default() -> Self {
56        Self::Options(HeaderSections::default())
57    }
58}
59
60#[derive(Debug, Clone, serde::Serialize, PartialEq, Eq)]
61pub struct EventTimeFormat {
62    pub hour: DateFormat,
63    pub minute: DateFormat,
64    pub meridiem: DateFormat,
65    #[serde(skip_serializing_if = "Option::is_none")]
66    pub omit_zero_minute: Option<bool>,
67}
68
69impl Default for EventTimeFormat {
70    fn default() -> Self {
71        Self {
72            hour: DateFormat::Numeric,
73            minute: DateFormat::TwoDigit,
74            meridiem: DateFormat::Short,
75            omit_zero_minute: None,
76        }
77    }
78}
79
80#[derive(Debug, Clone, serde::Serialize, PartialEq, Eq, Default)]
81#[serde(rename_all = "camelCase")]
82pub enum DateFormat {
83    #[default]
84    Short,
85    Numeric,
86    Long,
87    #[serde(rename = "2-digit")]
88    TwoDigit,
89    Narrow,
90    #[serde(untagged)]
91    False(bool),
92}
93
94impl AsRef<str> for DateFormat {
95    fn as_ref(&self) -> &str {
96        match self {
97            Self::Short => "short",
98            Self::Numeric => "numeric",
99            Self::Long => "long",
100            Self::TwoDigit => "2-digit",
101            Self::Narrow => "narrow",
102            Self::False(_) => "false",
103        }
104    }
105}
106
107#[derive(Debug, Clone, serde::Serialize, PartialEq, Eq, Default)]
108#[serde(rename_all = "camelCase")]
109pub struct DayHeaderFormat {
110    #[serde(skip_serializing_if = "Option::is_none")]
111    weekday: Option<DateFormat>,
112    #[serde(skip_serializing_if = "Option::is_none")]
113    month: Option<DateFormat>,
114    #[serde(skip_serializing_if = "Option::is_none")]
115    day: Option<DateFormat>,
116    #[serde(skip_serializing_if = "Option::is_none")]
117    omit_commas: Option<bool>,
118}
119
120impl DayHeaderFormat {
121    #[must_use]
122    pub const fn weekday(mut self, weekday: DateFormat) -> Self {
123        self.weekday = Some(weekday);
124        self
125    }
126    #[must_use]
127    pub const fn month(mut self, month: DateFormat) -> Self {
128        self.month = Some(month);
129        self
130    }
131    #[must_use]
132    pub const fn day(mut self, day: DateFormat) -> Self {
133        self.day = Some(day);
134        self
135    }
136    #[must_use]
137    pub const fn omit_commas(mut self, omit_commas: bool) -> Self {
138        self.omit_commas = Some(omit_commas);
139        self
140    }
141}
142
143#[derive(Debug, Clone, serde::Serialize, PartialEq, Eq, Default, serde::Deserialize)]
144#[serde(rename_all = "camelCase")]
145pub enum InitialView {
146    #[default]
147    DayGridMonth,
148    TimeGridWeek,
149    TimeGridDay,
150    ListMonth,
151    ListWeek,
152    ListDay,
153    ListYear,
154}
155impl AsRef<str> for InitialView {
156    fn as_ref(&self) -> &str {
157        match self {
158            Self::DayGridMonth => "dayGridMonth",
159            Self::TimeGridWeek => "timeGridWeek",
160            Self::TimeGridDay => "timeGridDay",
161            Self::ListMonth => "listMonth",
162            Self::ListWeek => "listWeek",
163            Self::ListDay => "listDay",
164            Self::ListYear => "listYear",
165        }
166    }
167}
168impl std::str::FromStr for InitialView {
169    type Err = String;
170    fn from_str(s: &str) -> Result<Self, Self::Err> {
171        match s {
172            "dayGridMonth" => Ok(Self::DayGridMonth),
173            "timeGridWeek" => Ok(Self::TimeGridWeek),
174            "timeGridDay" => Ok(Self::TimeGridDay),
175            "listMonth" => Ok(Self::ListMonth),
176            "listWeek" => Ok(Self::ListWeek),
177            "listDay" => Ok(Self::ListDay),
178            "listYear" => Ok(Self::ListYear),
179            _ => Err(format!("Invalid view name: {s}")),
180        }
181    }
182}
183
184#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, Default)]
185#[serde(rename_all = "camelCase")]
186pub enum Locale {
187    #[default]
188    #[serde(rename = "en")]
189    En,
190    #[serde(rename = "es")]
191    Es,
192    #[serde(rename = "fr")]
193    Fr,
194}
195
196impl AsRef<str> for Locale {
197    fn as_ref(&self) -> &str {
198        match self {
199            Self::En => "en",
200            Self::Es => "es",
201            Self::Fr => "fr",
202        }
203    }
204}
205
206#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, Default, serde::Deserialize)]
207pub enum TimeZone {
208    #[default]
209    #[serde(rename = "local")]
210    Local,
211    #[serde(rename = "UTC")]
212    Utc,
213    #[serde(untagged)]
214    NamedTimeZone(String),
215}
216
217impl std::str::FromStr for TimeZone {
218    type Err = String;
219    fn from_str(s: &str) -> Result<Self, Self::Err> {
220        Ok(match s {
221            "local" => Self::Local,
222            "UTC" => Self::Utc,
223            s => Self::NamedTimeZone(s.to_string()),
224        })
225    }
226}
227
228#[derive(Debug, Clone, serde::Serialize, PartialEq, Default)]
229#[serde(rename_all = "camelCase")]
230pub struct Options {
231    pub initial_view: InitialView,
232    #[serde(skip_serializing_if = "Option::is_none")]
233    pub initial_date: Option<crate::EventDate>,
234    #[serde(skip_serializing_if = "Option::is_none")]
235    pub locale: Option<Locale>,
236    #[serde(skip_serializing_if = "Option::is_none")]
237    pub time_zone: Option<TimeZone>,
238    #[serde(skip_serializing_if = "Option::is_none")]
239    pub expand_rows: Option<bool>,
240    #[serde(skip_serializing_if = "Option::is_none")]
241    pub all_day_slot: Option<bool>,
242    #[serde(skip_serializing_if = "Option::is_none")]
243    pub selectable: Option<bool>,
244    #[serde(skip_serializing_if = "Option::is_none")]
245    pub first_day: Option<u32>,
246    #[serde(skip_serializing_if = "Option::is_none")]
247    pub slot_duration: Option<crate::EventDuration>,
248    #[serde(skip_serializing_if = "Option::is_none")]
249    pub header_toolbar: Option<HeaderOptions>,
250    #[serde(skip_serializing_if = "Option::is_none")]
251    pub select_long_press_delay: Option<u32>,
252    #[serde(skip_serializing_if = "Option::is_none")]
253    pub display_event_time: Option<bool>,
254    #[serde(skip_serializing_if = "Option::is_none")]
255    pub event_time_format: Option<EventTimeFormat>,
256    #[serde(skip_serializing_if = "Option::is_none")]
257    pub day_headers: Option<bool>,
258    #[serde(skip_serializing_if = "Option::is_none")]
259    pub day_header_format: Option<DayHeaderFormat>,
260    #[serde(skip_serializing_if = "Option::is_none")]
261    pub slot_label_format: Option<EventTimeFormat>,
262    #[serde(skip)]
263    event_click_handler: Option<web_sys::js_sys::Function>,
264    #[serde(skip)]
265    select_handler: Option<web_sys::js_sys::Function>,
266    #[serde(skip)]
267    date_click_handler: Option<web_sys::js_sys::Function>,
268    #[serde(skip)]
269    date_set_handler: Option<web_sys::js_sys::Function>,
270}
271impl Options {
272    #[must_use]
273    pub fn new() -> Self {
274        Self::default()
275    }
276    #[must_use]
277    pub const fn with_initial_view(mut self, initial_view: InitialView) -> Self {
278        self.initial_view = initial_view;
279        self
280    }
281    #[must_use]
282    pub fn with_initial_date(mut self, initial_date: crate::EventDate) -> Self {
283        self.initial_date = Some(initial_date);
284        self
285    }
286    #[must_use]
287    pub const fn with_locale(mut self, locale: Locale) -> Self {
288        self.locale = Some(locale);
289        self
290    }
291    #[must_use]
292    pub fn with_time_zone(mut self, time_zone: TimeZone) -> Self {
293        self.time_zone.replace(time_zone);
294        self
295    }
296    #[must_use]
297    pub const fn with_expand_rows(mut self, expand_rows: bool) -> Self {
298        self.expand_rows = Some(expand_rows);
299        self
300    }
301    #[must_use]
302    pub const fn with_all_day_slot(mut self, all_day_slot: bool) -> Self {
303        self.all_day_slot = Some(all_day_slot);
304        self
305    }
306    #[must_use]
307    pub const fn with_selectable(mut self, selectable: bool) -> Self {
308        self.selectable = Some(selectable);
309        self
310    }
311    #[must_use]
312    pub const fn with_first_day(mut self, first_day: u32) -> Self {
313        self.first_day = Some(first_day);
314        self
315    }
316    #[must_use]
317    pub fn with_slot_duration(mut self, slot_duration: crate::EventDuration) -> Self {
318        self.slot_duration = Some(slot_duration);
319        self
320    }
321    #[must_use]
322    pub fn with_header_toolbar(mut self, header_toolbar: HeaderOptions) -> Self {
323        self.header_toolbar = Some(header_toolbar);
324        self
325    }
326    #[must_use]
327    pub const fn with_select_long_press_delay(mut self, select_long_press_delay: u32) -> Self {
328        self.select_long_press_delay = Some(select_long_press_delay);
329        self
330    }
331    #[must_use]
332    pub const fn with_display_event_time(mut self, display_event_time: bool) -> Self {
333        self.display_event_time = Some(display_event_time);
334        self
335    }
336    #[must_use]
337    pub const fn with_event_time_format(mut self, event_time_format: EventTimeFormat) -> Self {
338        self.event_time_format = Some(event_time_format);
339        self
340    }
341    #[must_use]
342    pub const fn with_day_headers(mut self, day_headers: bool) -> Self {
343        self.day_headers = Some(day_headers);
344        self
345    }
346    #[must_use]
347    pub const fn with_day_header_format(mut self, day_header_format: DayHeaderFormat) -> Self {
348        self.day_header_format = Some(day_header_format);
349        self
350    }
351    #[must_use]
352    pub const fn with_slot_label_format(mut self, slot_label_format: EventTimeFormat) -> Self {
353        self.slot_label_format = Some(slot_label_format);
354        self
355    }
356
357    #[must_use]
358    pub fn with_event_click<F>(mut self, f: F) -> Self
359    where
360        F: Fn(crate::bindings::EventClickInfo) + 'static,
361    {
362        let closure = web_sys::wasm_bindgen::closure::Closure::<
363            dyn Fn(crate::bindings::EventClickInfo),
364        >::new(f);
365        self.event_click_handler = Some(web_sys::wasm_bindgen::JsCast::unchecked_into(
366            closure.into_js_value(),
367        ));
368        self
369    }
370
371    #[must_use]
372    pub fn with_select<F>(mut self, f: F) -> Self
373    where
374        F: Fn(crate::bindings::SelectionInfo) + 'static,
375    {
376        let closure = web_sys::wasm_bindgen::closure::Closure::<
377            dyn Fn(crate::bindings::SelectionInfo),
378        >::new(f);
379        self.select_handler = Some(web_sys::wasm_bindgen::JsCast::unchecked_into(
380            closure.into_js_value(),
381        ));
382        self
383    }
384
385    #[must_use]
386    pub fn with_date_click<F>(mut self, f: F) -> Self
387    where
388        F: Fn(crate::bindings::DateClickInfo) + 'static,
389    {
390        let closure = web_sys::wasm_bindgen::closure::Closure::<
391            dyn Fn(crate::bindings::DateClickInfo),
392        >::new(f);
393        self.date_click_handler = Some(web_sys::wasm_bindgen::JsCast::unchecked_into(
394            closure.into_js_value(),
395        ));
396
397        self
398    }
399
400    #[must_use]
401    pub fn with_date_set<F>(mut self, f: F) -> Self
402    where
403        F: Fn(crate::bindings::DateSetEvent) + 'static,
404    {
405        let closure = web_sys::wasm_bindgen::closure::Closure::<
406            dyn Fn(crate::bindings::DateSetEvent),
407        >::new(f);
408        self.date_set_handler = Some(web_sys::wasm_bindgen::JsCast::unchecked_into(
409            closure.into_js_value(),
410        ));
411        self
412    }
413    fn validate_handler(&self) -> Result<(), web_sys::wasm_bindgen::JsValue> {
414        let validate_fn = |handler: &web_sys::js_sys::Function,
415                           name: &str|
416         -> Result<(), web_sys::wasm_bindgen::JsValue> {
417            if web_sys::js_sys::Reflect::get(handler, &"call".into())?.is_undefined() {
418                return Err(web_sys::wasm_bindgen::JsValue::from_str(&format!(
419                    "Invalid {name} handler"
420                )));
421            }
422            Ok(())
423        };
424
425        if let Some(handler) = &self.event_click_handler {
426            validate_fn(handler, "event click")?;
427        }
428        if let Some(handler) = &self.select_handler {
429            validate_fn(handler, "select")?;
430        }
431        if let Some(handler) = &self.date_click_handler {
432            validate_fn(handler, "date click")?;
433        }
434        if let Some(handler) = &self.date_set_handler {
435            validate_fn(handler, "date set")?;
436        }
437        Ok(())
438    }
439
440    /// Converts the options to a `JsValue`
441    ///
442    /// # Errors
443    ///
444    /// Returns an error if the options are invalid
445    pub fn build(self) -> Result<web_sys::wasm_bindgen::JsValue, web_sys::wasm_bindgen::JsValue> {
446        self.validate_handler()?;
447
448        let obj = web_sys::js_sys::Object::new();
449
450        // Convert the basic options
451        let base_options = serde_wasm_bindgen::to_value(&self)?;
452
453        // Copy properties
454        let base_obj: web_sys::js_sys::Object = base_options.into();
455        let keys = web_sys::js_sys::Object::keys(&base_obj);
456        for i in 0..keys.length() {
457            let key = keys.get(i);
458            let value = web_sys::js_sys::Reflect::get(&base_obj, &key)?;
459            web_sys::js_sys::Reflect::set(&obj, &key, &value)?;
460        }
461        // Add handlers
462        if let Some(handler) = &self.event_click_handler {
463            web_sys::js_sys::Reflect::set(
464                &obj,
465                &web_sys::wasm_bindgen::JsValue::from_str("eventClick"),
466                handler,
467            )?;
468        }
469        if let Some(handler) = &self.select_handler {
470            web_sys::js_sys::Reflect::set(
471                &obj,
472                &web_sys::wasm_bindgen::JsValue::from_str("select"),
473                handler,
474            )?;
475        }
476        if let Some(handler) = &self.date_click_handler {
477            web_sys::js_sys::Reflect::set(
478                &obj,
479                &web_sys::wasm_bindgen::JsValue::from_str("dateClick"),
480                handler,
481            )?;
482        }
483
484        if let Some(handler) = &self.date_set_handler {
485            web_sys::js_sys::Reflect::set(
486                &obj,
487                &web_sys::wasm_bindgen::JsValue::from_str("datesSet"),
488                handler,
489            )?;
490        }
491
492        Ok(obj.into())
493    }
494}