From 3699cf79d06d9e9cd0cf033c1d2f8b422344ec09 Mon Sep 17 00:00:00 2001 From: Dmitry Date: Sun, 7 Sep 2025 19:01:50 +0300 Subject: [PATCH 1/7] [*] DatePicker: implement new component - Select single day - Use time create for dates - Displaying a single month calendar view - No examples in docs - Fix CalendarSelectYear/CalendarSelectMonth date math bug --- README.md | 3 +- preview/src/components/date_picker/docs.md | 6 + .../date_picker/variants/main/mod.rs | 42 +++ .../date_picker/variants/main/style.css | 126 +++++++ preview/src/components/mod.rs | 1 + primitives/Cargo.toml | 2 +- primitives/src/calendar.rs | 15 +- primitives/src/date_picker.rs | 307 ++++++++++++++++++ primitives/src/lib.rs | 1 + 9 files changed, 497 insertions(+), 6 deletions(-) create mode 100644 preview/src/components/date_picker/docs.md create mode 100644 preview/src/components/date_picker/variants/main/mod.rs create mode 100644 preview/src/components/date_picker/variants/main/style.css create mode 100644 primitives/src/date_picker.rs diff --git a/README.md b/README.md index 9ddc5a85..51d7b1a5 100644 --- a/README.md +++ b/README.md @@ -33,7 +33,7 @@ Building styled and more featured component libraries on top of Dioxus Primitive We're still in the early days - Many components are still being created and stabilized. -28/28 +28/29 - [x] Accordion - [x] Alert Dialog @@ -43,6 +43,7 @@ We're still in the early days - Many components are still being created and stab - [x] Checkbox - [x] Collapsible - [x] Context Menu +- [ ] Date Picker - [x] Dialog - [x] Dropdown Menu - [x] Hover Card diff --git a/preview/src/components/date_picker/docs.md b/preview/src/components/date_picker/docs.md new file mode 100644 index 00000000..dfbe0232 --- /dev/null +++ b/preview/src/components/date_picker/docs.md @@ -0,0 +1,6 @@ +The DatePicker component is used to display a date input and a Calendar popover, allowing users to enter or select a date value. + +## Component Structure + +```rust +``` \ No newline at end of file diff --git a/preview/src/components/date_picker/variants/main/mod.rs b/preview/src/components/date_picker/variants/main/mod.rs new file mode 100644 index 00000000..afd30312 --- /dev/null +++ b/preview/src/components/date_picker/variants/main/mod.rs @@ -0,0 +1,42 @@ +use dioxus::prelude::*; +use dioxus_primitives::date_picker::{ + DatePicker, DatePickerCalendar, DatePickerInput, DatePickerTrigger, +}; + +use time::Date; + +#[component] +pub fn Demo() -> Element { + let mut selected_date = use_signal(|| None::); + + rsx! { + document::Link { + rel: "stylesheet", + href: asset!("/src/components/date_picker/variants/main/style.css"), + } + div { + DatePicker { + class: "date-picker", + selected_date: selected_date, + on_date_change: move |date| { + tracing::info!("Selected date: {:?}", date); + selected_date.set(date); + }, + DatePickerInput { + class: "date-picker-input", + DatePickerTrigger { + class: "date-picker-trigger", + aria_label: "DatePicker Trigger", + svg { + class: "date-picker-expand-icon", + view_box: "0 0 24 24", + xmlns: "http://www.w3.org/2000/svg", + polyline { points: "6 9 12 15 18 9" } + } + } + } + DatePickerCalendar { class: "date-picker-calendar" } + } + } + } +} diff --git a/preview/src/components/date_picker/variants/main/style.css b/preview/src/components/date_picker/variants/main/style.css new file mode 100644 index 00000000..ad0678f6 --- /dev/null +++ b/preview/src/components/date_picker/variants/main/style.css @@ -0,0 +1,126 @@ +.date-picker { + position: relative; + display: inline-flex; + align-items: center; +} + +.date-picker-input { + position: relative; + display: flex; + box-sizing: border-box; + flex-direction: row; + align-items: center; + justify-content: space-between; + padding: 0.25rem; + padding: 8px 40px 8px 12px; + border: none; + border-radius: 0.5rem; + border-radius: calc(0.5rem); + background: none; + background: var(--light, var(--primary-color)) + var(--dark, var(--primary-color-3)); + box-shadow: inset 0 0 0 1px var(--light, var(--primary-color-6)) + var(--dark, var(--primary-color-7)); + color: var(--secondary-color-4); + cursor: text; + gap: 0.25rem; + transition: background-color 100ms ease-out; +} + +.date-picker-trigger { + position: absolute; + right: 0; + border: none; + background-color: transparent; + cursor: pointer; + padding: 0.25rem; + padding: 8px 12px; +} + +.date-picker-input input::placeholder { + color: var(--secondary-color-5); +} + +.date-picker[data-state="open"] .date-picker-input { + pointer-events: none; +} + +.date-picker-expand-icon { + width: 20px; + height: 20px; + fill: none; + stroke: var(--primary-color-7); + stroke-linecap: round; + stroke-linejoin: round; + stroke-width: 2; +} + +.date-picker[data-disabled="true"] .date-picker-input { + color: var(--secondary-color-5); + cursor: not-allowed; +} + +.date-picker-input:hover:not([data-disabled="true"]), +.date-picker-input:focus-visible { + background: var(--light, var(--primary-color-4)) + var(--dark, var(--primary-color-5)); + color: var(--secondary-color-1); + outline: none; +} + +.date-picker-calendar { + position: absolute; + z-index: 1000; + top: 100%; + left: 0; + min-width: 100%; + box-sizing: border-box; + padding: 0.25rem; + border-radius: 0.5rem; + margin-top: 0.25rem; + background: var(--light, var(--primary-color)) + var(--dark, var(--primary-color-5)); + box-shadow: inset 0 0 0 1px var(--light, var(--primary-color-6)) + var(--dark, var(--primary-color-7)); + transform-origin: top; + opacity: 0; + pointer-events: none; + will-change: transform, opacity; +} + +.date-picker-calendar[data-state="closed"] { + pointer-events: none; + animation: date-picker-calendar-animate-out 150ms ease-in forwards; +} + +@keyframes date-picker-calendar-animate-out { + 0% { + opacity: 1; + transform: scale(1) translateY(0); + } + 100% { + opacity: 0; + transform: scale(0.95) translateY(-2px); + } +} + +.date-picker-calendar[data-state="open"] { + pointer-events: auto; + animation: date-picker-calendar-animate-in 150ms ease-out forwards; +} + +@keyframes date-picker-calendar-animate-in { + 0% { + opacity: 0; + transform: scale(0.95) translateY(-2px); + } + 100% { + opacity: 1; + transform: scale(1) translateY(0); + } +} + +[data-disabled="true"] { + cursor: not-allowed; + opacity: 0.5; +} diff --git a/preview/src/components/mod.rs b/preview/src/components/mod.rs index 1ea3b59d..bf41c474 100644 --- a/preview/src/components/mod.rs +++ b/preview/src/components/mod.rs @@ -63,6 +63,7 @@ examples!( checkbox, collapsible, context_menu, + date_picker, dialog, dropdown_menu, hover_card, diff --git a/primitives/Cargo.toml b/primitives/Cargo.toml index 3512968e..05a3b07e 100644 --- a/primitives/Cargo.toml +++ b/primitives/Cargo.toml @@ -14,7 +14,7 @@ repository = "https://github.com/DioxusLabs/components" [dependencies] dioxus.workspace = true dioxus-time = { git = "https://github.com/ealmloff/dioxus-std", branch = "0.7" } -time = { version = "0.3.41", features = ["std", "macros"] } +time = { version = "0.3.41", features = ["std", "macros", "parsing"] } tracing.workspace = true [build-dependencies] diff --git a/primitives/src/calendar.rs b/primitives/src/calendar.rs index 08f50f81..94561434 100644 --- a/primitives/src/calendar.rs +++ b/primitives/src/calendar.rs @@ -134,6 +134,13 @@ fn previous_month(date: Date) -> Option { .ok() } +fn replace_month(date: Date, month: Month) -> Date { + let year = date.year(); + let num_days = month.length(year); + Date::from_calendar_date(year, month, std::cmp::min(date.day(), num_days)) + .expect("invalid or out-of-range date") +} + /// The context provided by the [`Calendar`] component to its children. #[derive(Copy, Clone)] pub struct CalendarContext { @@ -1027,11 +1034,11 @@ pub fn CalendarSelectMonth(props: CalendarSelectMonthProps) -> Element { // Get the current view date from context let view_date = (calendar.view_date)(); let mut min_month = Month::January; - if view_date.replace_month(min_month).unwrap() < calendar.min_date { + if replace_month(view_date, min_month) < calendar.min_date { min_month = calendar.min_date.month(); } let mut max_month = Month::December; - if view_date.replace_month(max_month).unwrap() > calendar.max_date { + if replace_month(view_date, max_month) > calendar.max_date { max_month = calendar.max_date.month(); } @@ -1144,11 +1151,11 @@ pub fn CalendarSelectYear(props: CalendarSelectYearProps) -> Element { let view_date = (calendar.view_date)(); let month = view_date.month(); let mut min_year = calendar.min_date.year(); - if calendar.min_date.replace_month(month).unwrap() < calendar.min_date { + if replace_month(calendar.min_date, month) < calendar.min_date { min_year += 1; } let mut max_year = calendar.max_date.year(); - if calendar.max_date.replace_month(month).unwrap() > calendar.max_date { + if replace_month(calendar.max_date, month) > calendar.max_date { max_year -= 1; } diff --git a/primitives/src/date_picker.rs b/primitives/src/date_picker.rs new file mode 100644 index 00000000..081d7215 --- /dev/null +++ b/primitives/src/date_picker.rs @@ -0,0 +1,307 @@ +//! Defines the [`DatePicker`] component and its subcomponents, which allowing users to enter or select a date value + +use crate::calendar::{ + Calendar, CalendarGrid, CalendarHeader, CalendarNavigation, CalendarNextMonthButton, + CalendarPreviousMonthButton, CalendarSelectMonth, CalendarSelectYear, +}; +use crate::{ + focus::{use_focus_provider, FocusState}, + use_animated_open, use_effect, use_id_or, use_unique_id, +}; + +use dioxus::prelude::*; +use time::{macros::format_description, Date, UtcDateTime}; + +/// The context provided by the [`DatePicker`] component to its children. +#[derive(Copy, Clone)] +pub struct DatePickerContext { + /// The selected date + pub selected_date: Signal>, + + /// If the date select is open + pub open: Signal, + /// The ID of the calendar for ARIA attributes + pub calendar_id: Signal>, + /// The focus state for the date picker + focus_state: FocusState, + + disabled: ReadOnlySignal, +} + +/// The props for the [`DatePicker`] component. +#[derive(Props, Clone, PartialEq)] +pub struct DatePickerProps { + /// The selected date + pub selected_date: Signal>, + + /// Callback when selected date changes + #[props(default)] + pub on_date_change: Callback>, + + /// Whether focus should loop around when reaching the end + #[props(default = ReadOnlySignal::new(Signal::new(true)))] + pub roving_loop: ReadOnlySignal, + + /// Whether the date picker is disabled + #[props(default)] + pub disabled: ReadOnlySignal, + + /// Additional attributes to extend the date picker element + #[props(extends = GlobalAttributes)] + attributes: Vec, + + /// The children of the date picker element + children: Element, +} + +/// # DatePicker +/// +/// The [`DatePicker`] component provides an accessible date input interface. +/// +/// ## Example +/// ```rust +/// ``` +/// +/// # Styling +/// +/// The [`DatePicker`] component defines the following data attributes you can use to control styling: +/// - `data-disabled`: Indicates if the DatePicker is disabled. Possible values are `true` or `false`. +#[component] +pub fn DatePicker(props: DatePickerProps) -> Element { + let open = use_signal(|| false); + let calendar_id = use_signal(|| None); + let focus_state = use_focus_provider(props.roving_loop); + + // Create context provider for child components + use_context_provider(|| DatePickerContext { + open, + calendar_id, + focus_state, + selected_date: props.selected_date, + disabled: props.disabled, + }); + + rsx! { + div { + role: "application", + "aria-label": "DatePicker", + "data-disabled": (props.disabled)(), + ..props.attributes, + {props.children} + } + } +} + +/// The props for the [`SelectDateTrigger`] component +#[derive(Props, Clone, PartialEq)] +pub struct DatePickerTriggerProps { + /// Additional attributes for the trigger button + #[props(extends = GlobalAttributes)] + attributes: Vec, + + /// The children to render inside the trigger + children: Element, +} + +/// # DatePickerTrigger +/// +/// The trigger button for the [`DatePicker`](super::date_picker::DatePicker) component which controls if the [`DatePickerCalendar`](super::date_picker::DatePickerCalendar) is rendered. +/// +/// This must be used inside a [`DatePicker`](super::date_picker::DatePicker) component. +/// +/// ```rust +/// ``` +#[component] +pub fn DatePickerTrigger(props: DatePickerTriggerProps) -> Element { + let ctx = use_context::(); + let mut open = ctx.open; + + rsx! { + button { + // Standard HTML attributes + disabled: (ctx.disabled)(), + + onclick: move |_| { + open.toggle(); + }, + + // ARIA attributes + aria_haspopup: "calendarbox", + aria_expanded: open(), + aria_controls: ctx.calendar_id, + + // Pass through other attributes + ..props.attributes, + + // Render children (options) + {props.children} + } + } +} + +/// The props for the [`DatePickerInput`] component +#[derive(Props, Clone, PartialEq)] +pub struct DatePickerInputProps { + /// Additional attributes for the value element + #[props(extends = GlobalAttributes)] + attributes: Vec, + + /// The children of the date picker element + children: Element, +} + +/// # DatePickerInput +/// +/// The input element for the [`DatePicker`](super::date_picker::DatePicker) component which allow users to enter a date value. +/// +/// ```rust +/// ``` +#[component] +pub fn DatePickerInput(props: DatePickerInputProps) -> Element { + let mut ctx = use_context::(); + + let display_value = use_memo(move || match (ctx.selected_date)() { + Some(date) => date.to_string(), + None => String::default(), + }); + + rsx! { + input { + placeholder: "YYYY-MM-DD", + value: display_value, + oninput: move |e| { + let text = e.value().parse().unwrap_or(display_value()); + + let format = format_description!("[year]-[month]-[day]"); + if let Ok(date) = Date::parse(&text, &format) { + ctx.selected_date.set(Some(date)); + } + }, + ..props.attributes, + } + {props.children} + } +} + +/// The props for the [`DatePickerCalendar`] component +#[derive(Props, Clone, PartialEq)] +pub struct DatePickerCalendarProps { + /// The ID of the calendar for ARIA attributes + #[props(default)] + pub id: ReadOnlySignal>, + + /// Additional attributes for the list + #[props(extends = GlobalAttributes)] + attributes: Vec, +} + +/// # DatePickerCalendar +/// +/// The Calendar popover for the [`DatePicker`](super::date_picker::DatePicker). +/// The calendar will only be rendered when the DatePicker is open. +/// +/// This must be used inside a [`DatePicker`](super::date_picker::DatePicker). +/// +/// ## Example +/// +/// ```rust +/// ``` +#[component] +pub fn DatePickerCalendar(props: DatePickerCalendarProps) -> Element { + let mut ctx = use_context::(); + + let id = use_unique_id(); + let id = use_id_or(id, props.id); + use_effect(move || { + ctx.calendar_id.set(Some(id())); + }); + + let mut open = ctx.open; + let mut calendarbox_ref: Signal>> = use_signal(|| None); + let focused = move || open() && !ctx.focus_state.any_focused(); + + use_effect(move || { + let Some(calendarbox_ref) = calendarbox_ref() else { + return; + }; + if focused() { + spawn(async move { + _ = calendarbox_ref.set_focus(true); + }); + } + }); + + let onkeydown = move |event: KeyboardEvent| { + let key = event.key(); + + if key == Key::Escape { + open.set(false); + event.prevent_default(); + event.stop_propagation(); + } + }; + + let render = use_animated_open(id, open); + let render = use_memo(render); + + let mut view_date = use_signal(|| UtcDateTime::now().date()); + + use_effect(move || { + if let Some(date) = (ctx.selected_date)() { + view_date.set(date); + } + }); + + rsx! { + if render() { + div { + id, + role: "calendarbox", + tabindex: if focused() { "0" } else { "-1" }, + + // Data attributes + "data-state": if open() { "open" } else { "closed" }, + + onmounted: move |evt| calendarbox_ref.set(Some(evt.data())), + onkeydown, + + ..props.attributes, + + Calendar { + selected_date: (ctx.selected_date)(), + on_date_change: move |date| { + ctx.selected_date.set(date); + open.set(false); + }, + view_date: view_date(), + on_view_change: move |new_view: Date| { + view_date.set(new_view); + }, + CalendarHeader { + CalendarNavigation { + CalendarPreviousMonthButton { + svg { + class: "calendar-previous-month-icon", + view_box: "0 0 24 24", + xmlns: "http://www.w3.org/2000/svg", + polyline { points: "15 6 9 12 15 18" } + } + } + CalendarSelectMonth { class: "calendar-month-select" } + CalendarSelectYear { class: "calendar-year-select" } + CalendarNextMonthButton { + svg { + class: "calendar-next-month-icon", + view_box: "0 0 24 24", + xmlns: "http://www.w3.org/2000/svg", + polyline { points: "9 18 15 12 9 6" } + } + } + } + } + CalendarGrid {} + } + } + } + } +} diff --git a/primitives/src/lib.rs b/primitives/src/lib.rs index 63c2d5a4..4a085d17 100644 --- a/primitives/src/lib.rs +++ b/primitives/src/lib.rs @@ -14,6 +14,7 @@ pub mod calendar; pub mod checkbox; pub mod collapsible; pub mod context_menu; +pub mod date_picker; pub mod dialog; pub mod dropdown_menu; mod focus; From 473dd549b6d98410fffcd6ad2b0f101cc60337de Mon Sep 17 00:00:00 2001 From: Dmitry Date: Wed, 17 Sep 2025 17:34:10 +0300 Subject: [PATCH 2/7] [*] DatePicker: draft date range implementation --- .../date_picker/variants/main/mod.rs | 12 +- primitives/src/date_picker.rs | 145 +++++++++++++++--- 2 files changed, 125 insertions(+), 32 deletions(-) diff --git a/preview/src/components/date_picker/variants/main/mod.rs b/preview/src/components/date_picker/variants/main/mod.rs index afd30312..ee471318 100644 --- a/preview/src/components/date_picker/variants/main/mod.rs +++ b/preview/src/components/date_picker/variants/main/mod.rs @@ -1,13 +1,11 @@ use dioxus::prelude::*; use dioxus_primitives::date_picker::{ - DatePicker, DatePickerCalendar, DatePickerInput, DatePickerTrigger, + DatePicker, DatePickerCalendar, DatePickerInput, DatePickerTrigger, DatePickerValue, }; -use time::Date; - #[component] pub fn Demo() -> Element { - let mut selected_date = use_signal(|| None::); + let mut value = use_signal(|| None::); rsx! { document::Link { @@ -17,10 +15,10 @@ pub fn Demo() -> Element { div { DatePicker { class: "date-picker", - selected_date: selected_date, - on_date_change: move |date| { + value: value(), + on_value_change: move |date| { tracing::info!("Selected date: {:?}", date); - selected_date.set(date); + value.set(date); }, DatePickerInput { class: "date-picker-input", diff --git a/primitives/src/date_picker.rs b/primitives/src/date_picker.rs index 081d7215..147ac73f 100644 --- a/primitives/src/date_picker.rs +++ b/primitives/src/date_picker.rs @@ -1,10 +1,10 @@ //! Defines the [`DatePicker`] component and its subcomponents, which allowing users to enter or select a date value -use crate::calendar::{ - Calendar, CalendarGrid, CalendarHeader, CalendarNavigation, CalendarNextMonthButton, - CalendarPreviousMonthButton, CalendarSelectMonth, CalendarSelectYear, -}; use crate::{ + calendar::{ + Calendar, CalendarGrid, CalendarHeader, CalendarNavigation, CalendarNextMonthButton, + CalendarPreviousMonthButton, CalendarSelectMonth, CalendarSelectYear, + }, focus::{use_focus_provider, FocusState}, use_animated_open, use_effect, use_id_or, use_unique_id, }; @@ -12,31 +12,121 @@ use crate::{ use dioxus::prelude::*; use time::{macros::format_description, Date, UtcDateTime}; +/// The value of the [`DatePicker`] component. +#[derive(Debug, Clone, PartialEq)] +pub enum DatePickerValue { + /// A single value for the date picker + Single { + /// The selected date + date: Date, + }, + /// A dates range value for the date picker + Range { + /// The first range date + start: Date, + /// The last range date + end: Option, + }, +} + +impl DatePickerValue { + fn set(&self, date: Date) -> Self { + match self { + DatePickerValue::Single { .. } => DatePickerValue::Single { date }, + DatePickerValue::Range { start, end } => match end { + Some(_) => DatePickerValue::Range { + start: date, + end: None, + }, + None => { + if date < *start { + DatePickerValue::Range { + start: date, + end: Some(*start), + } + } else { + DatePickerValue::Range { + start: *start, + end: Some(date), + } + } + } + }, + } + } + + fn date(&self) -> Date { + match self { + DatePickerValue::Single { date } => *date, + DatePickerValue::Range { start, end } => { + if let Some(date) = end { + return *date; + } + + *start + } + } + } +} + +impl std::fmt::Display for DatePickerValue { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + DatePickerValue::Single { date } => write!(f, "{date}"), + DatePickerValue::Range { start, end } => { + let end_str = match end { + Some(value) => value.to_string(), + None => String::default(), + }; + write!(f, "{start} - {end_str}") + } + } + } +} + /// The context provided by the [`DatePicker`] component to its children. #[derive(Copy, Clone)] -pub struct DatePickerContext { - /// The selected date - pub selected_date: Signal>, - - /// If the date select is open - pub open: Signal, - /// The ID of the calendar for ARIA attributes - pub calendar_id: Signal>, - /// The focus state for the date picker +struct DatePickerContext { + // State + value: ReadOnlySignal>, + on_value_change: Callback>, + open: Signal, focus_state: FocusState, + // Configuration disabled: ReadOnlySignal, + calendar_id: Signal>, +} + +impl DatePickerContext { + fn set_date(&mut self, date: Option) { + match date { + Some(date) => match (self.value)() { + Some(value) => self.on_value_change.call(Some(value.set(date))), + None => { + tracing::error!("create new"); + self.on_value_change + .call(Some(DatePickerValue::Single { date })) + } + }, + None => self.on_value_change.call(None), + } + } } /// The props for the [`DatePicker`] component. #[derive(Props, Clone, PartialEq)] pub struct DatePickerProps { - /// The selected date - pub selected_date: Signal>, + /// The controlled value of the date picker + pub value: ReadOnlySignal>, - /// Callback when selected date changes + /// Callback when value changes #[props(default)] - pub on_date_change: Callback>, + pub on_value_change: Callback>, + + /// The selected date + #[props(default)] + pub selected_date: Signal>, /// Whether focus should loop around when reaching the end #[props(default = ReadOnlySignal::new(Signal::new(true)))] @@ -77,7 +167,8 @@ pub fn DatePicker(props: DatePickerProps) -> Element { open, calendar_id, focus_state, - selected_date: props.selected_date, + value: props.value, + on_value_change: props.on_value_change, disabled: props.disabled, }); @@ -160,8 +251,8 @@ pub struct DatePickerInputProps { pub fn DatePickerInput(props: DatePickerInputProps) -> Element { let mut ctx = use_context::(); - let display_value = use_memo(move || match (ctx.selected_date)() { - Some(date) => date.to_string(), + let display_value = use_memo(move || match (ctx.value)() { + Some(value) => value.to_string(), None => String::default(), }); @@ -174,7 +265,7 @@ pub fn DatePickerInput(props: DatePickerInputProps) -> Element { let format = format_description!("[year]-[month]-[day]"); if let Ok(date) = Date::parse(&text, &format) { - ctx.selected_date.set(Some(date)); + ctx.set_date(Some(date)); } }, ..props.attributes, @@ -244,12 +335,16 @@ pub fn DatePickerCalendar(props: DatePickerCalendarProps) -> Element { let render = use_animated_open(id, open); let render = use_memo(render); + let mut selected_date = use_signal(|| None::); let mut view_date = use_signal(|| UtcDateTime::now().date()); - use_effect(move || { - if let Some(date) = (ctx.selected_date)() { + use_effect(move || match (ctx.value)() { + Some(value) => { + let date = value.date(); view_date.set(date); + selected_date.set(Some(date)); } + None => selected_date.set(None), }); rsx! { @@ -268,9 +363,9 @@ pub fn DatePickerCalendar(props: DatePickerCalendarProps) -> Element { ..props.attributes, Calendar { - selected_date: (ctx.selected_date)(), + selected_date: selected_date(), on_date_change: move |date| { - ctx.selected_date.set(date); + ctx.set_date(date); open.set(false); }, view_date: view_date(), From 3c56fee5bbbdfacfae96be2ab0fc2f5925bebd44 Mon Sep 17 00:00:00 2001 From: Dmitry Date: Wed, 17 Sep 2025 20:38:55 +0300 Subject: [PATCH 3/7] [*] DatePicker: implement new functionality - Add popover with Calendar - Pass Calendar as child - Correct input validation - Split from preview - Fix add Calendar empty cells - Localization --- Cargo.lock | 1086 +++++++++-------- .../src/components/date_picker/component.rs | 92 ++ .../date_picker/{variants/main => }/style.css | 59 +- .../date_picker/variants/main/mod.rs | 45 +- preview/src/i18n/de-DE.ftl | 5 +- preview/src/i18n/en-US.ftl | 5 +- preview/src/i18n/es-ES.ftl | 5 +- preview/src/i18n/fr-FR.ftl | 5 +- primitives/src/calendar.rs | 19 +- primitives/src/date_picker.rs | 475 +++---- 10 files changed, 935 insertions(+), 861 deletions(-) create mode 100644 preview/src/components/date_picker/component.rs rename preview/src/components/date_picker/{variants/main => }/style.css (55%) diff --git a/Cargo.lock b/Cargo.lock index e843c4aa..ec84f9a8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -160,7 +160,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ef6978589202a00cd7e118380c448a08b6ed394c3a8df3a430d0898e3a42d046" dependencies = [ "android-properties", - "bitflags 2.9.1", + "bitflags 2.9.4", "cc", "cesu8", "jni", @@ -180,12 +180,6 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fc7eb209b1518d6bb87b283c20095f5228ecda460da70b44f0802523dea6da04" -[[package]] -name = "android-tzdata" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e999941b234f3131b00bc13c22d06e8c5ff726d1b6318ac7eb276997bbb4fef0" - [[package]] name = "android_system_properties" version = "0.1.5" @@ -216,15 +210,15 @@ dependencies = [ "image", "kurbo", "peniko", - "thiserror 2.0.14", + "thiserror 2.0.16", "usvg", ] [[package]] name = "anyrender_vello" -version = "0.5.1" +version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4b66b1f86022b8a391fcd98b6896515fad5587775ed0113844f1f59f22f81d8c" +checksum = "8c7bfb13ee0f53fb0bd500edb21f601e48e86d95d739c1a0a92579c7982b73d3" dependencies = [ "anyrender", "debug_timer", @@ -293,7 +287,7 @@ dependencies = [ "wayland-backend", "wayland-client", "wayland-protocols", - "zbus 5.9.0", + "zbus 5.11.0", ] [[package]] @@ -328,9 +322,9 @@ dependencies = [ [[package]] name = "async-executor" -version = "1.13.2" +version = "1.13.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bb812ffb58524bdd10860d7d974e2f01cc0950c2438a74ee5ec2e2280c6c4ffa" +checksum = "497c00e0fd83a72a79a39fcbd8e3e2f055d6f6c7e025f3b3d91f4f8e76527fb8" dependencies = [ "async-task", "concurrent-queue", @@ -342,9 +336,9 @@ dependencies = [ [[package]] name = "async-fs" -version = "2.1.3" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09f7e37c0ed80b2a977691c47dae8625cfb21e205827106c64f7c588766b2e50" +checksum = "8034a681df4aed8b8edbd7fbe472401ecf009251c8b40556b304567052e294c5" dependencies = [ "async-lock", "blocking", @@ -353,20 +347,20 @@ dependencies = [ [[package]] name = "async-io" -version = "2.5.0" +version = "2.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "19634d6336019ef220f09fd31168ce5c184b295cbf80345437cc36094ef223ca" +checksum = "456b8a8feb6f42d237746d4b3e9a178494627745c3c56c6ea55d92ba50d026fc" dependencies = [ - "async-lock", + "autocfg", "cfg-if", "concurrent-queue", "futures-io", "futures-lite", "parking", "polling", - "rustix 1.0.8", + "rustix 1.1.2", "slab", - "windows-sys 0.60.2", + "windows-sys 0.61.0", ] [[package]] @@ -382,9 +376,9 @@ dependencies = [ [[package]] name = "async-process" -version = "2.4.0" +version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "65daa13722ad51e6ab1a1b9c01299142bc75135b337923cfa10e79bbbd669f00" +checksum = "fc50921ec0055cdd8a16de48773bfeec5c972598674347252c0399676be7da75" dependencies = [ "async-channel", "async-io", @@ -395,7 +389,7 @@ dependencies = [ "cfg-if", "event-listener", "futures-lite", - "rustix 1.0.8", + "rustix 1.1.2", ] [[package]] @@ -406,14 +400,14 @@ checksum = "3b43422f69d8ff38f95f1b2bb76517c91589a924d1559a0e935d7c8ce0274c11" dependencies = [ "proc-macro2", "quote", - "syn 2.0.105", + "syn 2.0.106", ] [[package]] name = "async-signal" -version = "0.2.12" +version = "0.2.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f567af260ef69e1d52c2b560ce0ea230763e6fbb9214a85d768760a920e3e3c1" +checksum = "43c070bbf59cd3570b6b2dd54cd772527c7c3620fce8be898406dd3ed6adc64c" dependencies = [ "async-io", "async-lock", @@ -421,10 +415,10 @@ dependencies = [ "cfg-if", "futures-core", "futures-io", - "rustix 1.0.8", + "rustix 1.1.2", "signal-hook-registry", "slab", - "windows-sys 0.60.2", + "windows-sys 0.61.0", ] [[package]] @@ -441,7 +435,7 @@ checksum = "9035ad2d096bed7955a320ee7e2230574d28fd3c3a0f186cbea1ff3c7eed5dbb" dependencies = [ "proc-macro2", "quote", - "syn 2.0.105", + "syn 2.0.106", ] [[package]] @@ -653,9 +647,9 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" [[package]] name = "bitflags" -version = "2.9.1" +version = "2.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b8e56985ec62d17e9c1001dc89c88ecd7dc08e47eba5ec7c29c7b5eeecde967" +checksum = "2261d10cca569e4643e526d8dc2e62e433cc8aba21ab764233731f8d369bf394" dependencies = [ "serde", ] @@ -669,7 +663,7 @@ dependencies = [ "accesskit", "app_units", "atomic_refcell", - "bitflags 2.9.1", + "bitflags 2.9.4", "blitz-traits", "color", "cursor-icon", @@ -755,7 +749,7 @@ version = "0.1.0-rc.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4cb0235930120526087ce9d69fa2d6828e3fb078c61dde43231acec7968376bf" dependencies = [ - "bitflags 2.9.1", + "bitflags 2.9.4", "bytes", "cursor-icon", "http", @@ -834,7 +828,7 @@ checksum = "4f154e572231cb6ba2bd1176980827e3d5dc04cc183a75dea38109fbdd672d29" dependencies = [ "proc-macro2", "quote", - "syn 2.0.105", + "syn 2.0.106", ] [[package]] @@ -861,7 +855,7 @@ version = "0.18.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8ca26ef0159422fb77631dc9d17b102f253b876fe1586b03b803e63a309b4ee2" dependencies = [ - "bitflags 2.9.1", + "bitflags 2.9.4", "cairo-sys-rs", "glib", "libc", @@ -886,7 +880,7 @@ version = "0.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b99da2f8558ca23c71f4fd15dc57c906239752dd27ff3c00a1d56b685b7cbfec" dependencies = [ - "bitflags 2.9.1", + "bitflags 2.9.4", "log", "polling", "rustix 0.38.44", @@ -908,10 +902,11 @@ dependencies = [ [[package]] name = "cc" -version = "1.2.32" +version = "1.2.37" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2352e5597e9c544d5e6d9c95190d5d27738ade584fa8db0a16e130e5c2b5296e" +checksum = "65193589c6404eb80b450d618eaf9a2cafaaafd57ecce47370519ef674a7bd44" dependencies = [ + "find-msvc-tools", "jobserver", "libc", "shlex", @@ -946,9 +941,9 @@ dependencies = [ [[package]] name = "cfg-if" -version = "1.0.1" +version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9555578bc9e57714c812a1f84e4fc5b4d21fcb063490c624de019f7464c91268" +checksum = "2fd1289c04a9ea8cb22300a459a72a385d7c73d3259e2ed7dcb2af674838cfa9" [[package]] name = "cfg_aliases" @@ -958,16 +953,15 @@ checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" [[package]] name = "chrono" -version = "0.4.41" +version = "0.4.42" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c469d952047f47f91b68d1cba3f10d63c11d73e4636f24f08daf0278abf01c4d" +checksum = "145052bdd345b87320e369255277e3fb5152762ad123a901ef5c262dd38fe8d2" dependencies = [ - "android-tzdata", "iana-time-zone", "js-sys", "num-traits", "wasm-bindgen", - "windows-link", + "windows-link 0.2.0", ] [[package]] @@ -1003,7 +997,7 @@ version = "0.26.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ad36507aeb7e16159dfe68db81ccc27571c3ccd4b76fb2fb72fc59e7a4b1b64c" dependencies = [ - "bitflags 2.9.1", + "bitflags 2.9.4", "block", "cocoa-foundation", "core-foundation 0.10.1", @@ -1019,7 +1013,7 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "81411967c50ee9a1fc11365f8c585f863a22a9697c89239c452292c40ba79b0d" dependencies = [ - "bitflags 2.9.1", + "bitflags 2.9.4", "block", "core-foundation 0.10.1", "core-graphics-types 0.2.0", @@ -1038,9 +1032,9 @@ dependencies = [ [[package]] name = "color" -version = "0.3.1" +version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ae467d04a8a8aea5d9a49018a6ade2e4221d92968e8ce55a48c0b1164e5f698" +checksum = "a18ef4657441fb193b65f34dc39b3781f0dfec23d3bd94d0eeb4e88cde421edb" [[package]] name = "color_quant" @@ -1095,7 +1089,7 @@ checksum = "1da74b91de7c3426afaed28ed514bc4cd39821eeecf9f32dc424023310a63ae4" dependencies = [ "proc-macro2", "quote", - "syn 2.0.105", + "syn 2.0.106", ] [[package]] @@ -1194,7 +1188,7 @@ version = "0.24.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fa95a34622365fa5bbf40b20b75dba8dfa8c94c734aea8ac9a5ca38af14316f1" dependencies = [ - "bitflags 2.9.1", + "bitflags 2.9.4", "core-foundation 0.10.1", "core-graphics-types 0.2.0", "foreign-types 0.5.0", @@ -1218,7 +1212,7 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3d44a101f213f6c4cdc1853d4b78aef6db6bdfa3468798cc1d9912f4735013eb" dependencies = [ - "bitflags 2.9.1", + "bitflags 2.9.4", "core-foundation 0.10.1", "libc", ] @@ -1338,7 +1332,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "13b588ba4ac1a99f7f2964d24b3d896ddc6bf847ee3855dbd4366f058cfcd331" dependencies = [ "quote", - "syn 2.0.105", + "syn 2.0.106", ] [[package]] @@ -1353,8 +1347,18 @@ version = "0.20.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fc7f46116c46ff9ab3eb1597a45688b6715c6e628b5c133e288e709a29bcb4ee" dependencies = [ - "darling_core", - "darling_macro", + "darling_core 0.20.11", + "darling_macro 0.20.11", +] + +[[package]] +name = "darling" +version = "0.21.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9cdf337090841a411e2a7f3deb9187445851f91b309c0c0a29e05f74a00a48c0" +dependencies = [ + "darling_core 0.21.3", + "darling_macro 0.21.3", ] [[package]] @@ -1367,7 +1371,20 @@ dependencies = [ "ident_case", "proc-macro2", "quote", - "syn 2.0.105", + "syn 2.0.106", +] + +[[package]] +name = "darling_core" +version = "0.21.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1247195ecd7e3c85f83c8d2a366e4210d588e802133e1e355180a9870b517ea4" +dependencies = [ + "fnv", + "ident_case", + "proc-macro2", + "quote", + "syn 2.0.106", ] [[package]] @@ -1376,9 +1393,20 @@ version = "0.20.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fc34b93ccb385b40dc71c6fceac4b2ad23662c7eeb248cf10d529b7e055b6ead" dependencies = [ - "darling_core", + "darling_core 0.20.11", + "quote", + "syn 2.0.106", +] + +[[package]] +name = "darling_macro" +version = "0.21.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d38308df82d1080de0afee5d069fa14b0326a88c14f15c5ccda35b4a6c414c81" +dependencies = [ + "darling_core 0.21.3", "quote", - "syn 2.0.105", + "syn 2.0.106", ] [[package]] @@ -1403,15 +1431,15 @@ checksum = "2a2330da5de22e8a3cb63252ce2abb30116bf5265e89c0e01bc17015ce30a476" [[package]] name = "data-url" -version = "0.3.1" +version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c297a1c74b71ae29df00c3e22dd9534821d60eb9af5a0192823fa2acea70c2a" +checksum = "be1e0bca6c3637f992fc1cc7cbc52a78c1ef6db076dbf1059c4323d6a2048376" [[package]] name = "debug_timer" -version = "0.1.0" +version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bd6283985164a993d14300c81746aa6db2d889ed9fbb573fa20b40737c3a972c" +checksum = "666ffd4494c077ad572420cc52aded853e1cac859147455f82b0bc578b442076" [[package]] name = "deranged" @@ -1432,7 +1460,7 @@ dependencies = [ "proc-macro2", "quote", "rustc_version", - "syn 2.0.105", + "syn 2.0.106", ] [[package]] @@ -1452,7 +1480,7 @@ checksum = "bda628edc44c4bb645fbe0f758797143e4e07926f7ebf4e9bdfbd3d2ce621df3" dependencies = [ "proc-macro2", "quote", - "syn 2.0.105", + "syn 2.0.106", ] [[package]] @@ -1516,7 +1544,7 @@ dependencies = [ "ndk-context", "ndk-sys 0.6.0+11769913", "percent-encoding", - "thiserror 2.0.14", + "thiserror 2.0.16", "tokio", "wasm-bindgen-futures", "web-sys", @@ -1579,7 +1607,7 @@ dependencies = [ "dioxus-rsx", "proc-macro2", "quote", - "syn 2.0.105", + "syn 2.0.106", ] [[package]] @@ -1633,7 +1661,7 @@ dependencies = [ "slab", "subtle", "tao", - "thiserror 2.0.14", + "thiserror 2.0.16", "tokio", "tracing", "tray-icon", @@ -1655,7 +1683,7 @@ dependencies = [ "serde", "serde_json", "subsecond", - "thiserror 2.0.14", + "thiserror 2.0.16", "tracing", "tungstenite 0.27.0", "warnings", @@ -1809,7 +1837,7 @@ dependencies = [ "convert_case 0.8.0", "proc-macro2", "quote", - "syn 2.0.105", + "syn 2.0.106", ] [[package]] @@ -1819,7 +1847,7 @@ source = "git+https://github.com/dioxus-community/dioxus-i18n.git?branch=main#31 dependencies = [ "dioxus-lib", "fluent", - "thiserror 2.0.14", + "thiserror 2.0.16", "unic-langid", "walkdir", ] @@ -1854,7 +1882,7 @@ dependencies = [ "http", "lru", "rustc-hash 2.1.1", - "thiserror 2.0.14", + "thiserror 2.0.16", "tracing", "walkdir", ] @@ -1894,7 +1922,7 @@ dependencies = [ "serde", "serde_json", "slab", - "thiserror 2.0.14", + "thiserror 2.0.16", "tokio", "tokio-stream", "tokio-util", @@ -2003,7 +2031,7 @@ dependencies = [ "quote", "sha2", "slab", - "syn 2.0.105", + "syn 2.0.106", ] [[package]] @@ -2015,7 +2043,7 @@ dependencies = [ "proc-macro2", "proc-macro2-diagnostics", "quote", - "syn 2.0.105", + "syn 2.0.106", ] [[package]] @@ -2057,7 +2085,7 @@ dependencies = [ "serde", "server_fn", "subsecond", - "thiserror 2.0.14", + "thiserror 2.0.16", "tokio", "tokio-util", "tower", @@ -2116,7 +2144,7 @@ dependencies = [ "convert_case 0.8.0", "proc-macro2", "quote", - "syn 2.0.105", + "syn 2.0.106", ] [[package]] @@ -2172,7 +2200,7 @@ dependencies = [ "proc-macro2", "quote", "server_fn_macro", - "syn 2.0.105", + "syn 2.0.106", ] [[package]] @@ -2193,7 +2221,7 @@ dependencies = [ "libc", "option-ext", "redox_users", - "windows-sys 0.60.2", + "windows-sys 0.61.0", ] [[package]] @@ -2208,7 +2236,7 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "89a09f22a6c6069a18470eb92d2298acf25463f14256d24778e1230d789a2aec" dependencies = [ - "bitflags 2.9.1", + "bitflags 2.9.4", "block2 0.6.1", "libc", "objc2 0.6.2", @@ -2222,7 +2250,7 @@ checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" dependencies = [ "proc-macro2", "quote", - "syn 2.0.105", + "syn 2.0.106", ] [[package]] @@ -2236,9 +2264,9 @@ dependencies = [ [[package]] name = "dlopen2" -version = "0.7.0" +version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e1297103d2bbaea85724fcee6294c2d50b1081f9ad47d0f6f6f61eda65315a6" +checksum = "b54f373ccf864bf587a89e880fb7610f8d73f3045f13580948ccbcaff26febff" dependencies = [ "dlopen2_derive", "libc", @@ -2254,7 +2282,7 @@ checksum = "788160fb30de9cdd857af31c6a2675904b16ece8fc2737b2c7127ba368c9d0f4" dependencies = [ "proc-macro2", "quote", - "syn 2.0.105", + "syn 2.0.106", ] [[package]] @@ -2338,28 +2366,28 @@ checksum = "67c78a4d8fdf9953a5c9d458f9efe940fd97a0cab0941c075a813ac594733827" dependencies = [ "proc-macro2", "quote", - "syn 2.0.105", + "syn 2.0.106", ] [[package]] name = "enumset" -version = "1.1.7" +version = "1.1.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d6ee17054f550fd7400e1906e2f9356c7672643ed34008a9e8abe147ccd2d821" +checksum = "25b07a8dfbbbfc0064c0a6bdf9edcf966de6b1c33ce344bdeca3b41615452634" dependencies = [ "enumset_derive", ] [[package]] name = "enumset_derive" -version = "0.12.0" +version = "0.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "76d07902c93376f1e96c34abc4d507c0911df3816cef50b01f5a2ff3ad8c370d" +checksum = "f43e744e4ea338060faee68ed933e46e722fb7f3617e722a5772d7e856d8b3ce" dependencies = [ - "darling", + "darling 0.21.3", "proc-macro2", "quote", - "syn 2.0.105", + "syn 2.0.106", ] [[package]] @@ -2370,12 +2398,12 @@ checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" [[package]] name = "errno" -version = "0.3.13" +version = "0.3.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "778e2ac28f6c47af28e4907f13ffd1e1ddbd400980a9abd7c8df189bf578a5ad" +checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" dependencies = [ "libc", - "windows-sys 0.60.2", + "windows-sys 0.61.0", ] [[package]] @@ -2434,6 +2462,12 @@ dependencies = [ "rustc_version", ] +[[package]] +name = "find-msvc-tools" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7fd99930f64d146689264c637b5af2f0233a933bef0d8570e2526bf9e083192d" + [[package]] name = "flate2" version = "1.1.2" @@ -2492,7 +2526,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "54f0d287c53ffd184d04d8677f590f4ac5379785529e5e08b1c8083acdd5c198" dependencies = [ "memchr", - "thiserror 2.0.14", + "thiserror 2.0.16", ] [[package]] @@ -2565,7 +2599,7 @@ dependencies = [ "objc2-core-text", "objc2-foundation 0.3.1", "peniko", - "read-fonts", + "read-fonts 0.29.3", "roxmltree", "smallvec", "windows 0.58.0", @@ -2599,7 +2633,7 @@ checksum = "1a5c6c585bc94aaf2c7b51dd4c2ba22680844aba4c687be581871a6f518c5742" dependencies = [ "proc-macro2", "quote", - "syn 2.0.105", + "syn 2.0.106", ] [[package]] @@ -2616,9 +2650,9 @@ checksum = "aa9a19cbb55df58761df49b23516a86d432839add4af60fc256da840f66ed35b" [[package]] name = "form_urlencoded" -version = "1.2.1" +version = "1.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e13624c2627564efccf4934284bdd98cbaa14e79b0b5a141218e507b3a823456" +checksum = "cb4cb245038516f5f85277875cdaa4f7d2c9a0fa0468de06ed190163b1581fcf" dependencies = [ "percent-encoding", ] @@ -2713,7 +2747,7 @@ checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650" dependencies = [ "proc-macro2", "quote", - "syn 2.0.105", + "syn 2.0.106", ] [[package]] @@ -2862,19 +2896,19 @@ dependencies = [ [[package]] name = "gethostname" -version = "0.4.3" +version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0176e0459c2e4a1fe232f984bca6890e681076abb9934f6cea7c326f3fc47818" +checksum = "fc257fdb4038301ce4b9cd1b3b51704509692bb3ff716a410cbd07925d9dae55" dependencies = [ - "libc", - "windows-targets 0.48.5", + "rustix 1.1.2", + "windows-targets 0.52.6", ] [[package]] name = "getopts" -version = "0.2.23" +version = "0.2.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cba6ae63eb948698e300f645f87c70f76630d505f23b8907cf1e193ee85048c1" +checksum = "cfe4fbac503b8d1f88e6676011885f34b7174f46e59956bba534ba83abded4df" dependencies = [ "unicode-width 0.2.1", ] @@ -2910,7 +2944,7 @@ dependencies = [ "cfg-if", "libc", "r-efi", - "wasi 0.14.2+wasi-0.2.4", + "wasi 0.14.4+wasi-0.2.4", ] [[package]] @@ -2978,7 +3012,7 @@ version = "0.18.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "233daaf6e83ae6a12a52055f568f9d7cf4671dabb78ff9560ab6da230ce00ee5" dependencies = [ - "bitflags 2.9.1", + "bitflags 2.9.4", "futures-channel", "futures-core", "futures-executor", @@ -3002,11 +3036,11 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0bb0228f477c0900c880fd78c8759b95c7636dbd7842707f49e132378aa2acdc" dependencies = [ "heck 0.4.1", - "proc-macro-crate 2.0.0", + "proc-macro-crate 2.0.2", "proc-macro-error", "proc-macro2", "quote", - "syn 2.0.105", + "syn 2.0.106", ] [[package]] @@ -3030,7 +3064,7 @@ dependencies = [ "objc2 0.6.2", "objc2-app-kit 0.3.1", "once_cell", - "thiserror 2.0.14", + "thiserror 2.0.16", "windows-sys 0.59.0", "x11rb", "xkeysym", @@ -3120,7 +3154,7 @@ version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fbcd2dba93594b227a1f57ee09b8b9da8892c34d55aa332e034a228d0fe6a171" dependencies = [ - "bitflags 2.9.1", + "bitflags 2.9.4", "gpu-alloc-types", ] @@ -3130,7 +3164,7 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "98ff03b468aa837d70984d55f5d3f846f6ec31fe34bbb97c4f85219caeee1ca4" dependencies = [ - "bitflags 2.9.1", + "bitflags 2.9.4", ] [[package]] @@ -3151,7 +3185,7 @@ version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b89c83349105e3732062a895becfc71a8f921bb71ecbbdd8ff99263e3b53a0ca" dependencies = [ - "bitflags 2.9.1", + "bitflags 2.9.4", "gpu-descriptor-types", "hashbrown 0.15.5", ] @@ -3162,7 +3196,7 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fdf242682df893b86f33a73828fb09ca4b2d3bb6cc95249707fc684d27484b91" dependencies = [ - "bitflags 2.9.1", + "bitflags 2.9.4", ] [[package]] @@ -3220,7 +3254,7 @@ dependencies = [ "proc-macro-error", "proc-macro2", "quote", - "syn 2.0.105", + "syn 2.0.106", ] [[package]] @@ -3384,13 +3418,14 @@ checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" [[package]] name = "hyper" -version = "1.6.0" +version = "1.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cc2b571658e38e0c01b1fdca3bbbe93c00d3d71693ff2770043f8c29bc7d6f80" +checksum = "eb3aa54a13a0dfe7fbe3a59e0c76093041720fdc77b110cc0fc260fafb4dc51e" dependencies = [ + "atomic-waker", "bytes", "futures-channel", - "futures-util", + "futures-core", "h2", "http", "http-body", @@ -3398,6 +3433,7 @@ dependencies = [ "httpdate", "itoa", "pin-project-lite", + "pin-utils", "smallvec", "tokio", "want", @@ -3437,9 +3473,9 @@ dependencies = [ [[package]] name = "hyper-util" -version = "0.1.16" +version = "0.1.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8d9b05277c7e8da2c93a568989bb6207bef0112e8d17df7a6eda4a3cf143bc5e" +checksum = "3c6995591a8f1380fcb4ba966a252a4b29188d51d2b89e3a252f5305be65aea8" dependencies = [ "base64", "bytes", @@ -3463,9 +3499,9 @@ dependencies = [ [[package]] name = "iana-time-zone" -version = "0.1.63" +version = "0.1.64" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b0c919e5debc312ad217002b8048a17b7d83f80703865bbfcfebb0458b0b27d8" +checksum = "33e57f83510bb73707521ebaffa789ec8caf86f9657cad665b092b581d40e9fb" dependencies = [ "android_system_properties", "core-foundation-sys", @@ -3600,7 +3636,7 @@ checksum = "1ec89e9337638ecdc08744df490b221a7399bf8d164eb52a665454e60e075ad6" dependencies = [ "proc-macro2", "quote", - "syn 2.0.105", + "syn 2.0.106", ] [[package]] @@ -3633,9 +3669,9 @@ checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" [[package]] name = "idna" -version = "1.0.3" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "686f825264d630750a544639377bae737628043f20d38bbc029e8f29ea968a7e" +checksum = "3b0875f23caa03898994f6ddc501886a45c7d3d62d04d2d90788d47be1b1e4de" dependencies = [ "idna_adapter", "smallvec", @@ -3687,18 +3723,18 @@ checksum = "edcd27d72f2f071c64249075f42e205ff93c9a4c5f6c6da53e79ed9f9832c285" [[package]] name = "immutable-chunkmap" -version = "2.0.6" +version = "2.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "12f97096f508d54f8f8ab8957862eee2ccd628847b6217af1a335e1c44dee578" +checksum = "9a3e98b1520e49e252237edc238a39869da9f3241f2ec19dc788c1d24694d1e4" dependencies = [ "arrayvec", ] [[package]] name = "indexmap" -version = "2.10.0" +version = "2.11.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fe4cd85333e22411419a0bcae1297d25e58c9443848b11dc6a86fefe8c78a661" +checksum = "92119844f513ffa41556430369ab02c295a3578af21cf945caa3e9e0c2481ac3" dependencies = [ "equivalent", "hashbrown 0.15.5", @@ -3734,20 +3770,20 @@ dependencies = [ [[package]] name = "inventory" -version = "0.3.20" +version = "0.3.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ab08d7cd2c5897f2c949e5383ea7c7db03fb19130ffcfbf7eda795137ae3cb83" +checksum = "bc61209c082fbeb19919bee74b176221b27223e27b65d781eb91af24eb1fb46e" dependencies = [ "rustversion", ] [[package]] name = "io-uring" -version = "0.7.9" +version = "0.7.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d93587f37623a1a17d94ef2bc9ada592f5465fe7732084ab7beefabe5c77c0c4" +checksum = "046fa2d4d00aea763528b4950358d0ead425372445dc8ff86312b3c69ff7727b" dependencies = [ - "bitflags 2.9.1", + "bitflags 2.9.4", "cfg-if", "libc", ] @@ -3830,9 +3866,9 @@ checksum = "8eaf4bc02d17cbdd7ff4c7438cafcdf7fb9a4613313ad11b4f8fefe7d3fa0130" [[package]] name = "jobserver" -version = "0.1.33" +version = "0.1.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38f262f097c174adebe41eb73d66ae9c06b2844fb0da69969647bbddd9b0538a" +checksum = "9afb3de4395d6b3e67a780b6de64b51c978ecf11cb9a462c66be7d4ca9039d33" dependencies = [ "getrandom 0.3.3", "libc", @@ -3840,9 +3876,9 @@ dependencies = [ [[package]] name = "js-sys" -version = "0.3.77" +version = "0.3.80" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1cfaf33c695fc6e08064efbc1f72ec937429614f25eef83af942d0e227c3a28f" +checksum = "852f13bec5eba4ba9afbeb93fd7c13fe56147f055939ae21c43a29a0ecb2702e" dependencies = [ "once_cell", "wasm-bindgen", @@ -3854,7 +3890,7 @@ version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b750dcadc39a09dbadd74e118f6dd6598df77fa01df0cfcdc52c28dece74528a" dependencies = [ - "bitflags 2.9.1", + "bitflags 2.9.4", "serde", "unicode-segmentation", ] @@ -3975,11 +4011,11 @@ checksum = "f9fbbcab51052fe104eb5e5d351cf728d30a5be1fe14d9be8a3b097481fb97de" [[package]] name = "libredox" -version = "0.1.9" +version = "0.1.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "391290121bad3d37fbddad76d8f5d1c1c314cfc646d143d7e07a3086ddff0ce3" +checksum = "416f7e718bdb06000964960ffa43b4335ad4012ae8b99060261aa4a8088d5ccb" dependencies = [ - "bitflags 2.9.1", + "bitflags 2.9.4", "libc", "redox_syscall 0.5.17", ] @@ -4003,6 +4039,12 @@ dependencies = [ "x11", ] +[[package]] +name = "linebender_resource_handle" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4a5ff6bcca6c4867b1c4fd4ef63e4db7436ef363e0ad7531d1558856bae64f4" + [[package]] name = "linked-hash-map" version = "0.5.6" @@ -4017,9 +4059,9 @@ checksum = "d26c52dbd32dccf2d10cac7725f8eae5296885fb5703b261f7d0a0739ec807ab" [[package]] name = "linux-raw-sys" -version = "0.9.4" +version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cd945864f07fe9f5371a27ad7b52a172b4b499999f1d97574c9fa68373937e12" +checksum = "df1d3c3b53da64cf5760482273a98e575c651a67eec7f77df96b5b642de8f039" [[package]] name = "litemap" @@ -4045,9 +4087,9 @@ dependencies = [ [[package]] name = "log" -version = "0.4.27" +version = "0.4.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "13dc2df351e3202783a1fe0d44375f7295ffb4049267b0f3018346dc122a1d94" +checksum = "34080505efa8e45a4b816c349525ebe327ceaa8559756f0356cba97ef3bf7432" [[package]] name = "longest-increasing-subsequence" @@ -4057,9 +4099,9 @@ checksum = "b3bd0dd2cd90571056fdb71f6275fada10131182f84899f4b2a916e565d81d86" [[package]] name = "lru" -version = "0.16.0" +version = "0.16.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "86ea4e65087ff52f3862caff188d489f1fab49a0cb09e01b2e3f1a617b10aaed" +checksum = "bfe949189f46fabb938b3a9a0be30fdd93fd8a09260da863399a8cf3db756ec8" dependencies = [ "hashbrown 0.15.5", ] @@ -4078,7 +4120,7 @@ checksum = "1b27834086c65ec3f9387b096d66e99f221cf081c2b738042aa252bcd41204e3" dependencies = [ "proc-macro2", "quote", - "syn 2.0.105", + "syn 2.0.106", ] [[package]] @@ -4097,7 +4139,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f44db74bde26fdf427af23f1d146c211aed857c59e3be750cf2617f6b0b05c94" dependencies = [ "proc-macro2", - "syn 2.0.105", + "syn 2.0.106", "synstructure", ] @@ -4135,7 +4177,7 @@ dependencies = [ "manganis-core", "proc-macro2", "quote", - "syn 2.0.105", + "syn 2.0.106", ] [[package]] @@ -4171,16 +4213,16 @@ checksum = "88a9689d8d44bf9964484516275f5cd4c9b59457a6940c1d5d0ecbb94510a36b" dependencies = [ "proc-macro2", "quote", - "syn 2.0.105", + "syn 2.0.106", ] [[package]] name = "matchers" -version = "0.1.0" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8263075bb86c5a1b1427b5ae862e8889656f126e9f77c484496e8b47cf5c5558" +checksum = "d1525a2a28c7f4fa0fc98bb91ae755d1e2d1505079e05539e35bc876b5d65ae9" dependencies = [ - "regex-automata 0.1.10", + "regex-automata", ] [[package]] @@ -4203,18 +4245,18 @@ checksum = "32a282da65faaf38286cf3be983213fcf1d2e2a58700e808f83f4ea9a4804bc0" [[package]] name = "memfd" -version = "0.6.4" +version = "0.6.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b2cffa4ad52c6f791f4f8b15f0c05f9824b2ced1160e88cc393d64fff9a8ac64" +checksum = "ad38eb12aea514a0466ea40a80fd8cc83637065948eb4a426e4aa46261175227" dependencies = [ - "rustix 0.38.44", + "rustix 1.1.2", ] [[package]] name = "memmap2" -version = "0.9.7" +version = "0.9.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "483758ad303d734cec05e5c12b41d7e93e6a6390c5e9dae6bdeb7c1259012d28" +checksum = "843a98750cd611cc2965a8213b53b43e715f13c37a9e096c6408e69990961db7" dependencies = [ "libc", ] @@ -4234,7 +4276,7 @@ version = "0.31.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f569fb946490b5743ad69813cb19629130ce9374034abe31614a36402d18f99e" dependencies = [ - "bitflags 2.9.1", + "bitflags 2.9.4", "block", "core-graphics-types 0.1.3", "foreign-types 0.5.0", @@ -4297,7 +4339,7 @@ dependencies = [ "objc2-foundation 0.3.1", "once_cell", "png", - "thiserror 2.0.14", + "thiserror 2.0.16", "windows-sys 0.60.2", ] @@ -4326,7 +4368,7 @@ checksum = "e380993072e52eef724eddfcde0ed013b0c023c3f0417336ed041aa9f076994e" dependencies = [ "arrayvec", "bit-set", - "bitflags 2.9.1", + "bitflags 2.9.4", "cfg_aliases", "codespan-reporting", "hexf-parse", @@ -4336,7 +4378,7 @@ dependencies = [ "spirv", "strum 0.26.3", "termcolor", - "thiserror 2.0.14", + "thiserror 2.0.16", "unicode-xid", ] @@ -4363,7 +4405,7 @@ version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c3f42e7bbe13d351b6bead8286a43aac9534b82bd3cc43e47037f012ebfd62d4" dependencies = [ - "bitflags 2.9.1", + "bitflags 2.9.4", "jni-sys", "log", "ndk-sys 0.6.0+11769913", @@ -4408,7 +4450,7 @@ version = "0.29.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "71e2746dc3a24dd78b3cfcb7be93368c6de9963d30f43a6a73998a9cf4b17b46" dependencies = [ - "bitflags 2.9.1", + "bitflags 2.9.4", "cfg-if", "cfg_aliases", "libc", @@ -4421,7 +4463,7 @@ version = "0.30.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "74523f3a35e05aba87a1d978330aef40f67b0304ac79c1c00b294c9830543db6" dependencies = [ - "bitflags 2.9.1", + "bitflags 2.9.4", "cfg-if", "cfg_aliases", "libc", @@ -4448,7 +4490,7 @@ checksum = "ed3955f1a9c7c0c15e092f9c887db08b1fc683305fdf6eb6684f22555355e202" dependencies = [ "proc-macro2", "quote", - "syn 2.0.105", + "syn 2.0.106", ] [[package]] @@ -4495,10 +4537,10 @@ version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "77e878c846a8abae00dd069496dbe8751b16ac1c3d6bd2a7283a938e8228f90d" dependencies = [ - "proc-macro-crate 3.3.0", + "proc-macro-crate 3.4.0", "proc-macro2", "quote", - "syn 2.0.105", + "syn 2.0.106", ] [[package]] @@ -4542,7 +4584,7 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e4e89ad9e3d7d297152b17d39ed92cd50ca8063a89a9fa569046d41568891eff" dependencies = [ - "bitflags 2.9.1", + "bitflags 2.9.4", "block2 0.5.1", "libc", "objc2 0.5.2", @@ -4558,7 +4600,7 @@ version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e6f29f568bec459b0ddff777cec4fe3fd8666d82d5a40ebd0ff7e66134f89bcc" dependencies = [ - "bitflags 2.9.1", + "bitflags 2.9.4", "block2 0.6.1", "objc2 0.6.2", "objc2-core-foundation", @@ -4571,7 +4613,7 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "74dd3b56391c7a0596a295029734d3c1c5e7e510a4cb30245f8221ccea96b009" dependencies = [ - "bitflags 2.9.1", + "bitflags 2.9.4", "block2 0.5.1", "objc2 0.5.2", "objc2-core-location", @@ -4595,7 +4637,7 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "617fbf49e071c178c0b24c080767db52958f716d9eabdf0890523aeae54773ef" dependencies = [ - "bitflags 2.9.1", + "bitflags 2.9.4", "block2 0.5.1", "objc2 0.5.2", "objc2-foundation 0.2.2", @@ -4607,7 +4649,7 @@ version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1c10c2894a6fed806ade6027bcd50662746363a9589d3ec9d9bef30a4e4bc166" dependencies = [ - "bitflags 2.9.1", + "bitflags 2.9.4", "dispatch2", "objc2 0.6.2", ] @@ -4618,7 +4660,7 @@ version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "989c6c68c13021b5c2d6b71456ebb0f9dc78d752e86a98da7c716f4f9470f5a4" dependencies = [ - "bitflags 2.9.1", + "bitflags 2.9.4", "objc2-core-foundation", ] @@ -4652,7 +4694,7 @@ version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3ba833d4a1cb1aac330f8c973fd92b6ff1858e4aef5cdd00a255eefb28022fb5" dependencies = [ - "bitflags 2.9.1", + "bitflags 2.9.4", "objc2-core-foundation", ] @@ -4677,7 +4719,7 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0ee638a5da3799329310ad4cfa62fbf045d5f56e3ef5ba4149e7452dcf89d5a8" dependencies = [ - "bitflags 2.9.1", + "bitflags 2.9.4", "block2 0.5.1", "dispatch", "libc", @@ -4690,7 +4732,7 @@ version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "900831247d2fe1a09a683278e5384cfb8c80c79fe6b166f9d14bfdde0ea1b03c" dependencies = [ - "bitflags 2.9.1", + "bitflags 2.9.4", "block2 0.6.1", "objc2 0.6.2", "objc2-core-foundation", @@ -4714,7 +4756,7 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dd0cba1276f6023976a406a14ffa85e1fdd19df6b0f737b063b95f6c8c7aadd6" dependencies = [ - "bitflags 2.9.1", + "bitflags 2.9.4", "block2 0.5.1", "objc2 0.5.2", "objc2-foundation 0.2.2", @@ -4726,7 +4768,7 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e42bee7bff906b14b167da2bac5efe6b6a07e6f7c0a21a7308d40c960242dc7a" dependencies = [ - "bitflags 2.9.1", + "bitflags 2.9.4", "block2 0.5.1", "objc2 0.5.2", "objc2-foundation 0.2.2", @@ -4749,7 +4791,7 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b8bb46798b20cd6b91cbd113524c490f1686f4c4e8f49502431415f3512e2b6f" dependencies = [ - "bitflags 2.9.1", + "bitflags 2.9.4", "block2 0.5.1", "objc2 0.5.2", "objc2-cloud-kit", @@ -4770,7 +4812,7 @@ version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "25b1312ad7bc8a0e92adae17aa10f90aae1fb618832f9b993b022b591027daed" dependencies = [ - "bitflags 2.9.1", + "bitflags 2.9.4", "objc2 0.6.2", "objc2-core-foundation", "objc2-foundation 0.3.1", @@ -4793,7 +4835,7 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "76cfcbf642358e8689af64cee815d139339f3ed8ad05103ed5eaf73db8d84cb3" dependencies = [ - "bitflags 2.9.1", + "bitflags 2.9.4", "block2 0.5.1", "objc2 0.5.2", "objc2-core-location", @@ -4806,7 +4848,7 @@ version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "91672909de8b1ce1c2252e95bbee8c1649c9ad9d14b9248b3d7b4c47903c47ad" dependencies = [ - "bitflags 2.9.1", + "bitflags 2.9.4", "block2 0.6.1", "objc2 0.6.2", "objc2-app-kit 0.3.1", @@ -4844,7 +4886,7 @@ version = "6.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "336b9c63443aceef14bea841b899035ae3abe89b7c486aaf4c5bd8aafedac3f0" dependencies = [ - "bitflags 2.9.1", + "bitflags 2.9.4", "libc", "once_cell", "onig_sys", @@ -4866,7 +4908,7 @@ version = "0.10.73" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8505734d46c8ab1e19a1dce3aef597ad87dcb4c37e7188231769bd6bd51cebf8" dependencies = [ - "bitflags 2.9.1", + "bitflags 2.9.4", "cfg-if", "foreign-types 0.3.2", "libc", @@ -4883,7 +4925,7 @@ checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" dependencies = [ "proc-macro2", "quote", - "syn 2.0.105", + "syn 2.0.106", ] [[package]] @@ -5010,7 +5052,7 @@ dependencies = [ "fontique", "hashbrown 0.15.5", "peniko", - "skrifa", + "skrifa 0.31.3", "swash", ] @@ -5022,20 +5064,21 @@ checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" [[package]] name = "peniko" -version = "0.4.0" +version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1f9529efd019889b2a205193c14ffb6e2839b54ed9d2720674f10f4b04d87ac9" +checksum = "9b44f9ddd2f480176b34278eb653ec1c8062f3b143a4e16eeff5ffac3334e288" dependencies = [ "color", "kurbo", + "linebender_resource_handle", "smallvec", ] [[package]] name = "percent-encoding" -version = "2.3.1" +version = "2.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" +checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" [[package]] name = "phf" @@ -5141,7 +5184,7 @@ dependencies = [ "phf_shared 0.11.3", "proc-macro2", "quote", - "syn 2.0.105", + "syn 2.0.106", ] [[package]] @@ -5194,7 +5237,7 @@ checksum = "6e918e4ff8c4549eb882f14b3a4bc8c8bc93de829416eacf579f1207a8fbf861" dependencies = [ "proc-macro2", "quote", - "syn 2.0.105", + "syn 2.0.106", ] [[package]] @@ -5234,7 +5277,7 @@ checksum = "3af6b589e163c5a788fab00ce0c0366f6efbb9959c2f9874b224936af7fce7e1" dependencies = [ "base64", "indexmap", - "quick-xml 0.38.1", + "quick-xml 0.38.3", "serde", "time", ] @@ -5254,16 +5297,16 @@ dependencies = [ [[package]] name = "polling" -version = "3.10.0" +version = "3.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b5bd19146350fe804f7cb2669c851c03d69da628803dab0d98018142aaa5d829" +checksum = "5d0e4f59085d47d8241c88ead0f274e8a0cb551f3625263c05eb8dd897c34218" dependencies = [ "cfg-if", "concurrent-queue", "hermit-abi", "pin-project-lite", - "rustix 1.0.8", - "windows-sys 0.60.2", + "rustix 1.1.2", + "windows-sys 0.61.0", ] [[package]] @@ -5326,20 +5369,21 @@ dependencies = [ [[package]] name = "proc-macro-crate" -version = "2.0.0" +version = "2.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7e8366a6159044a37876a2b9817124296703c586a5c92e2c53751fa06d8d43e8" +checksum = "b00f26d3400549137f92511a46ac1cd8ce37cb5598a96d382381458b992a5d24" dependencies = [ - "toml_edit 0.20.7", + "toml_datetime 0.6.3", + "toml_edit 0.20.2", ] [[package]] name = "proc-macro-crate" -version = "3.3.0" +version = "3.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "edce586971a4dfaa28950c6f18ed55e0406c1ab88bbce2c6f6293a7aaba73d35" +checksum = "219cb19e96be00ab2e37d6e299658a0cfa83e52429179969b0f0121b4ac46983" dependencies = [ - "toml_edit 0.22.27", + "toml_edit 0.23.5", ] [[package]] @@ -5374,9 +5418,9 @@ checksum = "dc375e1527247fe1a97d8b7156678dfe7c1af2fc075c9a4db3690ecd2a148068" [[package]] name = "proc-macro2" -version = "1.0.97" +version = "1.0.101" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d61789d7719defeb74ea5fe81f2fdfdbd28a803847077cecce2ff14e1472f6f1" +checksum = "89ae43fd86e4158d6db51ad8e2b80f313af9cc74f5c0e03ccb87de09998732de" dependencies = [ "unicode-ident", ] @@ -5389,7 +5433,7 @@ checksum = "af066a9c399a26e020ada66a034357a868728e72cd426f3adcd35f80d88d88c8" dependencies = [ "proc-macro2", "quote", - "syn 2.0.105", + "syn 2.0.106", "version_check", ] @@ -5405,7 +5449,7 @@ version = "0.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1e8bbe1a966bd2f362681a44f6edce3c2310ac21e4d5067a6e7ec396297a6ea0" dependencies = [ - "bitflags 2.9.1", + "bitflags 2.9.4", "getopts", "memchr", "pulldown-cmark-escape", @@ -5445,9 +5489,9 @@ dependencies = [ [[package]] name = "quick-xml" -version = "0.38.1" +version = "0.38.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9845d9dccf565065824e69f9f235fafba1587031eda353c1f1561cd6a6be78f4" +checksum = "42a232e7487fc2ef313d96dde7948e7a3c05101870d8985e4fd8d26aedd27b89" dependencies = [ "memchr", ] @@ -5625,6 +5669,16 @@ dependencies = [ "font-types", ] +[[package]] +name = "read-fonts" +version = "0.33.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "50ea612a55c08586a1d15134be8a776186c440c312ebda3b9e8efbfe4255b7f4" +dependencies = [ + "bytemuck", + "font-types", +] + [[package]] name = "redox_syscall" version = "0.4.1" @@ -5640,7 +5694,7 @@ version = "0.5.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5407465600fb0548f1442edf71dd20683c6ed326200ace4b1ef0763521bb3b77" dependencies = [ - "bitflags 2.9.1", + "bitflags 2.9.4", ] [[package]] @@ -5651,52 +5705,25 @@ checksum = "a4e608c6638b9c18977b00b475ac1f28d14e84b27d8d42f70e0bf1e3dec127ac" dependencies = [ "getrandom 0.2.16", "libredox", - "thiserror 2.0.14", -] - -[[package]] -name = "regex" -version = "1.11.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191" -dependencies = [ - "aho-corasick", - "memchr", - "regex-automata 0.4.9", - "regex-syntax 0.8.5", -] - -[[package]] -name = "regex-automata" -version = "0.1.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c230d73fb8d8c1b9c0b3135c5142a8acee3a0558fb8db5cf1cb65f8d7862132" -dependencies = [ - "regex-syntax 0.6.29", + "thiserror 2.0.16", ] [[package]] name = "regex-automata" -version = "0.4.9" +version = "0.4.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "809e8dc61f6de73b46c85f4c96486310fe304c434cfa43669d7b40f711150908" +checksum = "6b9458fa0bfeeac22b5ca447c63aaf45f28439a709ccd244698632f9aa6394d6" dependencies = [ "aho-corasick", "memchr", - "regex-syntax 0.8.5", + "regex-syntax", ] [[package]] name = "regex-syntax" -version = "0.6.29" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f162c6dd7b008981e4d40210aca20b4bd0f9b60ca9271061b07f78537722f2e1" - -[[package]] -name = "regex-syntax" -version = "0.8.5" +version = "0.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" +checksum = "caf4aa5b0f434c91fe5c7f1ecb6a5ece2130b02ad2a590589dda5146df959001" [[package]] name = "renderdoc-sys" @@ -5825,7 +5852,7 @@ version = "0.38.44" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fdb5bc1ae2baa591800df16c9ca78619bf65c0488b41b96ccec5d11220d8c154" dependencies = [ - "bitflags 2.9.1", + "bitflags 2.9.4", "errno", "libc", "linux-raw-sys 0.4.15", @@ -5834,15 +5861,15 @@ dependencies = [ [[package]] name = "rustix" -version = "1.0.8" +version = "1.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "11181fbabf243db407ef8df94a6ce0b2f9a733bd8be4ad02b4eda9602296cac8" +checksum = "cd15f8a2c5551a84d56efdc1cd049089e409ac19a3072d5037a17fd70719ff3e" dependencies = [ - "bitflags 2.9.1", + "bitflags 2.9.4", "errno", "libc", - "linux-raw-sys 0.9.4", - "windows-sys 0.60.2", + "linux-raw-sys 0.11.0", + "windows-sys 0.61.0", ] [[package]] @@ -5869,9 +5896,9 @@ dependencies = [ [[package]] name = "rustls-webpki" -version = "0.103.4" +version = "0.103.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0a17884ae0c1b773f1ccd2bd4a8c72f16da897310a98b0e84bf349ad5ead92fc" +checksum = "8572f3c2cb9934231157b45499fc41e1f58c589fdfb81a844ba873265e80f8eb" dependencies = [ "ring", "rustls-pki-types", @@ -5890,7 +5917,7 @@ version = "0.20.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fd3c7c96f8a08ee34eff8857b11b49b07d71d1c3f4e88f8a88d4c9e9f90b1702" dependencies = [ - "bitflags 2.9.1", + "bitflags 2.9.4", "bytemuck", "core_maths", "log", @@ -5919,11 +5946,11 @@ dependencies = [ [[package]] name = "schannel" -version = "0.1.27" +version = "0.1.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1f29ebaa345f945cec9fbbc532eb307f0fdad8161f281b6369539c8d84876b3d" +checksum = "891d81b926048e76efe18581bf793546b4c0eaf8448d72be8de2bbee5fd166e1" dependencies = [ - "windows-sys 0.59.0", + "windows-sys 0.61.0", ] [[package]] @@ -5957,7 +5984,7 @@ version = "2.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "897b2245f0b511c87893af39b033e5ca9cce68824c4d7e7630b5a1d339658d02" dependencies = [ - "bitflags 2.9.1", + "bitflags 2.9.4", "core-foundation 0.9.4", "core-foundation-sys", "libc", @@ -5966,9 +5993,9 @@ dependencies = [ [[package]] name = "security-framework-sys" -version = "2.14.0" +version = "2.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49db231d56a190491cb4aeda9527f1ad45345af50b0851622a7adb8c03b01c32" +checksum = "cc1f0cbffaac4852523ce30d8bd3c5cdc873501d96ff467ca09b6767bb8cd5c0" dependencies = [ "core-foundation-sys", "libc", @@ -5998,7 +6025,7 @@ version = "0.29.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d61a96a0a2d04f964888e003ec83e3172159a16e81b35de9f6ab85d8767cf2b1" dependencies = [ - "bitflags 2.9.1", + "bitflags 2.9.4", "cssparser 0.35.0", "derive_more 2.0.1", "fxhash", @@ -6021,9 +6048,9 @@ checksum = "0f7d95a54511e0c7be3f51e8867aa8cf35148d7b9445d44de2f943e2b206e749" [[package]] name = "semver" -version = "1.0.26" +version = "1.0.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "56e6fa9c48d24d85fb3de5ad847117517440f6beceb7798af16b4a87d616b8d0" +checksum = "d767eb0aabc880b29956c35734170f26ed551a859dbd361d140cdbeca61ab1e2" [[package]] name = "send_wrapper" @@ -6036,10 +6063,11 @@ dependencies = [ [[package]] name = "serde" -version = "1.0.219" +version = "1.0.225" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f0e2c6ed6606019b4e29e69dbaba95b11854410e5347d525002456dbbb786b6" +checksum = "fd6c24dee235d0da097043389623fb913daddf92c76e9f5a1db88607a0bcbd1d" dependencies = [ + "serde_core", "serde_derive", ] @@ -6054,37 +6082,48 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "serde_core" +version = "1.0.225" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "659356f9a0cb1e529b24c01e43ad2bdf520ec4ceaf83047b83ddcc2251f96383" +dependencies = [ + "serde_derive", +] + [[package]] name = "serde_derive" -version = "1.0.219" +version = "1.0.225" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00" +checksum = "0ea936adf78b1f766949a4977b91d2f5595825bd6ec079aa9543ad2685fc4516" dependencies = [ "proc-macro2", "quote", - "syn 2.0.105", + "syn 2.0.106", ] [[package]] name = "serde_json" -version = "1.0.142" +version = "1.0.145" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "030fedb782600dcbd6f02d479bf0d817ac3bb40d644745b769d6a96bc3afc5a7" +checksum = "402a6f66d8c709116cf22f558eab210f5a50187f702eb4d7e5ef38d9a7f1c79c" dependencies = [ "itoa", "memchr", "ryu", "serde", + "serde_core", ] [[package]] name = "serde_path_to_error" -version = "0.1.17" +version = "0.1.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "59fab13f937fa393d08645bf3a84bdfe86e296747b506ada67bb15f10f218b2a" +checksum = "10a9ff822e371bb5403e391ecd83e182e0e77ba7f6fe0160b795797109d1b457" dependencies = [ "itoa", "serde", + "serde_core", ] [[package]] @@ -6095,7 +6134,7 @@ checksum = "f3faaf9e727533a19351a43cc5a8de957372163c7d35cc48c90b75cdda13c352" dependencies = [ "percent-encoding", "serde", - "thiserror 2.0.14", + "thiserror 2.0.16", ] [[package]] @@ -6106,7 +6145,7 @@ checksum = "175ee3e80ae9982737ca543e96133087cbd9a485eecc3bc4de9c1a37b47ea59c" dependencies = [ "proc-macro2", "quote", - "syn 2.0.105", + "syn 2.0.106", ] [[package]] @@ -6158,7 +6197,7 @@ dependencies = [ "serde_json", "serde_qs", "server_fn_macro_default", - "thiserror 2.0.14", + "thiserror 2.0.16", "throw_error", "tokio", "tokio-tungstenite 0.27.0", @@ -6183,7 +6222,7 @@ dependencies = [ "proc-macro2", "quote", "rustc_version", - "syn 2.0.105", + "syn 2.0.106", "xxhash-rust", ] @@ -6194,7 +6233,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ca7abc92ed696648275ed9ff171131a83d571af11748593dc2e6eb6a4e22a5b9" dependencies = [ "server_fn_macro", - "syn 2.0.105", + "syn 2.0.106", ] [[package]] @@ -6307,7 +6346,17 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dbeb4ca4399663735553a09dd17ce7e49a0a0203f03b706b39628c4d913a8607" dependencies = [ "bytemuck", - "read-fonts", + "read-fonts 0.29.3", +] + +[[package]] +name = "skrifa" +version = "0.35.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "576e60c7de4bb6a803a0312f9bef17e78cf1e8d25a80e1ade76770d7a0237955" +dependencies = [ + "bytemuck", + "read-fonts 0.33.1", ] [[package]] @@ -6333,7 +6382,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f62f06db0370222f7f498ef478fce9f8df5828848d1d3517e3331936d7074f55" dependencies = [ "quote", - "syn 2.0.105", + "syn 2.0.106", ] [[package]] @@ -6373,7 +6422,7 @@ version = "0.19.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3457dea1f0eb631b4034d61d4d8c32074caa6cd1ab2d59f2327bd8461e2c0016" dependencies = [ - "bitflags 2.9.1", + "bitflags 2.9.4", "calloop", "calloop-wayland-source", "cursor-icon", @@ -6449,7 +6498,7 @@ version = "0.3.0+sdk-1.3.268.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "eda41003dc44290527a59b13432d4a0379379fa074b70174882adfbdfd917844" dependencies = [ - "bitflags 2.9.1", + "bitflags 2.9.4", ] [[package]] @@ -6526,7 +6575,7 @@ dependencies = [ "proc-macro2", "quote", "rustversion", - "syn 2.0.105", + "syn 2.0.106", ] [[package]] @@ -6538,7 +6587,7 @@ dependencies = [ "heck 0.5.0", "proc-macro2", "quote", - "syn 2.0.105", + "syn 2.0.106", ] [[package]] @@ -6550,7 +6599,7 @@ dependencies = [ "app_units", "arrayvec", "atomic_refcell", - "bitflags 2.9.1", + "bitflags 2.9.4", "byteorder", "cssparser 0.35.0", "derive_more 2.0.1", @@ -6621,10 +6670,10 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8384a2ebb61abbde4466dfc9d5dcda134dfa939b77bfb2df878d1c7dffe081f1" dependencies = [ - "darling", + "darling 0.20.11", "proc-macro2", "quote", - "syn 2.0.105", + "syn 2.0.106", "synstructure", ] @@ -6634,7 +6683,7 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d6bce5a8c7b6d5657952caeeec50213d356d1ca2f2b042cf4594a897701282e9" dependencies = [ - "bitflags 2.9.1", + "bitflags 2.9.4", "stylo_malloc_size_of", ] @@ -6679,7 +6728,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "244bfd473f321ff0258f5b2986e218d5f72507fa405fd131b8dbb5ebac166663" dependencies = [ "app_units", - "bitflags 2.9.1", + "bitflags 2.9.4", "cssparser 0.35.0", "euclid", "malloc_size_of_derive", @@ -6707,7 +6756,7 @@ dependencies = [ "memmap2", "serde", "subsecond-types", - "thiserror 2.0.14", + "thiserror 2.0.16", "wasm-bindgen", "wasm-bindgen-futures", "web-sys", @@ -6750,7 +6799,7 @@ version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f745de914febc7c9ab4388dfaf94bbc87e69f57bb41133a9b0c84d4be49856f3" dependencies = [ - "skrifa", + "skrifa 0.31.3", "yazi", "zeno", ] @@ -6768,9 +6817,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.105" +version = "2.0.106" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7bc3fcb250e53458e712715cf74285c1f889686520d79294a9ef3bd7aa1fc619" +checksum = "ede7c438028d4436d71104916910f5bb611972c5cfd7f89b8300a8186e6fada6" dependencies = [ "proc-macro2", "quote", @@ -6794,7 +6843,7 @@ checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2" dependencies = [ "proc-macro2", "quote", - "syn 2.0.105", + "syn 2.0.106", ] [[package]] @@ -6810,7 +6859,7 @@ dependencies = [ "once_cell", "onig", "plist", - "regex-syntax 0.8.5", + "regex-syntax", "serde", "serde_derive", "serde_json", @@ -6825,7 +6874,7 @@ version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3c879d448e9d986b661742763247d3693ed13609438cf3d006f51f5368a5ba6b" dependencies = [ - "bitflags 2.9.1", + "bitflags 2.9.4", "core-foundation 0.9.4", "system-configuration-sys", ] @@ -6867,11 +6916,12 @@ dependencies = [ [[package]] name = "tao" -version = "0.34.0" +version = "0.34.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49c380ca75a231b87b6c9dd86948f035012e7171d1a7c40a9c2890489a7ffd8a" +checksum = "959469667dbcea91e5485fc48ba7dd6023face91bb0f1a14681a70f99847c3f7" dependencies = [ - "bitflags 2.9.1", + "bitflags 2.9.4", + "block2 0.6.1", "core-foundation 0.10.1", "core-graphics 0.24.0", "crossbeam-channel", @@ -6913,7 +6963,7 @@ checksum = "f4e16beb8b2ac17db28eab8bca40e62dbfbb34c0fcdc6d9826b11b7b5d047dfd" dependencies = [ "proc-macro2", "quote", - "syn 2.0.105", + "syn 2.0.106", ] [[package]] @@ -6924,15 +6974,15 @@ checksum = "61c41af27dd6d1e27b1b16b489db798443478cef1f06a660c96db617ba5de3b1" [[package]] name = "tempfile" -version = "3.20.0" +version = "3.22.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e8a64e3985349f2441a1a9ef0b853f869006c3855f2cda6862a94d26ebb9d6a1" +checksum = "84fa4d11fadde498443cca10fd3ac23c951f0dc59e080e9f4b93d4df4e4eea53" dependencies = [ "fastrand", "getrandom 0.3.3", "once_cell", - "rustix 1.0.8", - "windows-sys 0.59.0", + "rustix 1.1.2", + "windows-sys 0.61.0", ] [[package]] @@ -6972,11 +7022,11 @@ dependencies = [ [[package]] name = "thiserror" -version = "2.0.14" +version = "2.0.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b0949c3a6c842cbde3f1686d6eea5a010516deb7085f79db747562d4102f41e" +checksum = "3467d614147380f2e4e374161426ff399c91084acd2363eaf549172b3d5e60c0" dependencies = [ - "thiserror-impl 2.0.14", + "thiserror-impl 2.0.16", ] [[package]] @@ -6987,18 +7037,18 @@ checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" dependencies = [ "proc-macro2", "quote", - "syn 2.0.105", + "syn 2.0.106", ] [[package]] name = "thiserror-impl" -version = "2.0.14" +version = "2.0.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cc5b44b4ab9c2fdd0e0512e6bece8388e214c0749f5862b114cc5b7a25daf227" +checksum = "6c5e1be1c48b9172ee610da68fd9cd2770e7a4056cb3fc98710ee6906f0c7960" dependencies = [ "proc-macro2", "quote", - "syn 2.0.105", + "syn 2.0.106", ] [[package]] @@ -7097,9 +7147,9 @@ dependencies = [ [[package]] name = "tinyvec" -version = "1.9.0" +version = "1.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09b3661f17e86524eccd4371ab0429194e0d7c008abb45f7a7495b1719463c71" +checksum = "bfa5fdc3bce6191a1dbc8c02d5c8bffcf557bafa17c124c5264a458f1b0613fa" dependencies = [ "tinyvec_macros", ] @@ -7130,10 +7180,10 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2ba1f5563024b63bb6acb4558452d9ba737518c1d11fcc1861febe98d1e31cf4" dependencies = [ - "darling", + "darling 0.20.11", "proc-macro2", "quote", - "syn 2.0.105", + "syn 2.0.106", "synstructure", ] @@ -7165,7 +7215,7 @@ checksum = "6e06d43f1345a3bcd39f6a56dbb7dcab2ba47e68e8ac134855e7e2bdbaf8cab8" dependencies = [ "proc-macro2", "quote", - "syn 2.0.105", + "syn 2.0.106", ] [[package]] @@ -7180,9 +7230,9 @@ dependencies = [ [[package]] name = "tokio-rustls" -version = "0.26.2" +version = "0.26.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e727b36a1a0e8b74c376ac2211e40c2c8af09fb4013c60d910495810f008e9b" +checksum = "05f63835928ca123f1bef57abbcd23bb2ba0ac9ae1235f1e65bda0d06e7786bd" dependencies = [ "rustls", "tokio", @@ -7239,25 +7289,34 @@ dependencies = [ [[package]] name = "toml" -version = "0.8.23" +version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc1beb996b9d83529a9e75c17a1686767d148d70663143c7854d8b4a09ced362" +checksum = "185d8ab0dfbb35cf1399a6344d8484209c088f75f8f68230da55d48d95d43e3d" dependencies = [ "serde", "serde_spanned", - "toml_datetime", - "toml_edit 0.22.27", + "toml_datetime 0.6.3", + "toml_edit 0.20.2", ] [[package]] name = "toml_datetime" -version = "0.6.11" +version = "0.6.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22cddaf88f4fbc13c51aebbf5f8eceb5c7c5a9da2ac40a13519eb5b0a0e8f11c" +checksum = "7cda73e2f1397b1262d6dfdcef8aafae14d1de7748d66822d3bfeeb6d03e5e4b" dependencies = [ "serde", ] +[[package]] +name = "toml_datetime" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a197c0ec7d131bfc6f7e82c8442ba1595aeab35da7adbf05b6b73cd06a16b6be" +dependencies = [ + "serde_core", +] + [[package]] name = "toml_edit" version = "0.19.15" @@ -7265,32 +7324,42 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1b5bb770da30e5cbfde35a2d7b9b8a2c4b8ef89548a7a6aeab5c9a576e3e7421" dependencies = [ "indexmap", - "toml_datetime", + "toml_datetime 0.6.3", "winnow 0.5.40", ] [[package]] name = "toml_edit" -version = "0.20.7" +version = "0.20.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "70f427fce4d84c72b5b732388bf4a9f4531b53f74e2887e3ecb2481f68f66d81" +checksum = "396e4d48bbb2b7554c944bde63101b5ae446cff6ec4a24227428f15eb72ef338" dependencies = [ "indexmap", - "toml_datetime", + "serde", + "serde_spanned", + "toml_datetime 0.6.3", "winnow 0.5.40", ] [[package]] name = "toml_edit" -version = "0.22.27" +version = "0.23.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "41fe8c660ae4257887cf66394862d21dbca4a6ddd26f04a3560410406a2f819a" +checksum = "c2ad0b7ae9cfeef5605163839cb9221f453399f15cfb5c10be9885fcf56611f9" dependencies = [ "indexmap", - "serde", - "serde_spanned", - "toml_datetime", - "winnow 0.7.12", + "toml_datetime 0.7.1", + "toml_parser", + "winnow 0.7.13", +] + +[[package]] +name = "toml_parser" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b551886f449aa90d4fe2bdaa9f4a2577ad2dde302c61ecf262d80b116db95c10" +dependencies = [ + "winnow 0.7.13", ] [[package]] @@ -7315,7 +7384,7 @@ version = "0.6.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "adc82fd73de2a9722ac5da747f12383d2bfdb93591ee6c58486e0097890f05f2" dependencies = [ - "bitflags 2.9.1", + "bitflags 2.9.4", "bytes", "futures-core", "futures-util", @@ -7369,7 +7438,7 @@ checksum = "81383ab64e72a7a8b8e13130c49e3dab29def6d0c7d76a03087b3cf71c5c6903" dependencies = [ "proc-macro2", "quote", - "syn 2.0.105", + "syn 2.0.106", ] [[package]] @@ -7393,13 +7462,13 @@ dependencies = [ [[package]] name = "tracing-subscriber" -version = "0.3.19" +version = "0.3.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e8189decb5ac0fa7bc8b96b7cb9b2701d60d48805aca84a238004d665fcc4008" +checksum = "2054a14f5307d601f88daf0553e1cbf472acc4f2c51afab632431cdcd72124d5" dependencies = [ "matchers", "once_cell", - "regex", + "regex-automata", "sharded-slab", "thread_local", "tracing", @@ -7434,7 +7503,7 @@ dependencies = [ "objc2-foundation 0.3.1", "once_cell", "png", - "thiserror 2.0.14", + "thiserror 2.0.16", "windows-sys 0.59.0", ] @@ -7466,7 +7535,7 @@ dependencies = [ "log", "rand 0.9.2", "sha1", - "thiserror 2.0.14", + "thiserror 2.0.16", "utf-8", ] @@ -7485,7 +7554,7 @@ dependencies = [ "rand 0.9.2", "rustls", "sha1", - "thiserror 2.0.14", + "thiserror 2.0.16", "utf-8", ] @@ -7563,7 +7632,7 @@ checksum = "a1249a628de3ad34b821ecb1001355bca3940bcb2f88558f1a8bd82e977f75b5" dependencies = [ "proc-macro-hack", "quote", - "syn 2.0.105", + "syn 2.0.106", "unic-langid-impl", ] @@ -7593,9 +7662,9 @@ checksum = "ce61d488bcdc9bc8b5d1772c404828b17fc481c0a582b5581e95fb233aef503e" [[package]] name = "unicode-ident" -version = "1.0.18" +version = "1.0.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512" +checksum = "f63a545481291138910575129486daeaf8ac54aee4387fe7906919f7830c7d9d" [[package]] name = "unicode-properties" @@ -7647,9 +7716,9 @@ checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" [[package]] name = "url" -version = "2.5.4" +version = "2.5.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32f8b686cadd1473f4bd0117a5d28d36b1ade384ea9b5069a1c40aefed7fda60" +checksum = "08bc136a29a3d1758e07a9cca267be308aeebf5cfd5a10f3f67ab2097683ef5b" dependencies = [ "form_urlencoded", "idna", @@ -7716,9 +7785,9 @@ checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" [[package]] name = "uuid" -version = "1.18.0" +version = "1.18.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f33196643e165781c20a5ead5582283a7dacbb87855d867fbc2df3f81eddc1be" +checksum = "2f87b8aa10b915a06587d0dec516c282ff295b475d94abf425d62b57710070a2" dependencies = [ "js-sys", "wasm-bindgen", @@ -7732,18 +7801,18 @@ checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" [[package]] name = "vello" -version = "0.5.0" +version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df026e62e8b0d12d55ff5e91ae9114a20f82d9b856bedfdbf3abd5d1472dceed" +checksum = "fa3f8a53870a2ee699ce05b738a3f9974c92c35ed4874de86052ac68d214811c" dependencies = [ "bytemuck", "futures-intrusive", "log", "peniko", "png", - "skrifa", + "skrifa 0.35.0", "static_assertions", - "thiserror 2.0.14", + "thiserror 2.0.16", "vello_encoding", "vello_shaders", "wgpu", @@ -7751,26 +7820,26 @@ dependencies = [ [[package]] name = "vello_encoding" -version = "0.5.0" +version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c5702642f6ea77eedc12d119e1eebead0dba3cf91fe5c5d1f3efc12bf0cfaf1" +checksum = "c69b0fe94b0ac7e47619c504ee2c377355174f5c46353c46d03fa5f7e435922b" dependencies = [ "bytemuck", "guillotiere", "peniko", - "skrifa", + "skrifa 0.35.0", "smallvec", ] [[package]] name = "vello_shaders" -version = "0.5.0" +version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "381790a3779021edd9f88267c1b13b49546cb0fb164f329ee2f2587869ddf459" +checksum = "b2ebea426bb2f95b7610bca09178b03d809ede1d3c500a9acf6eca43e8f200be" dependencies = [ "bytemuck", "naga", - "thiserror 2.0.14", + "thiserror 2.0.16", "vello_encoding", ] @@ -7830,7 +7899,7 @@ checksum = "59195a1db0e95b920366d949ba5e0d3fc0e70b67c09be15ce5abb790106b0571" dependencies = [ "proc-macro2", "quote", - "syn 2.0.105", + "syn 2.0.106", ] [[package]] @@ -7847,44 +7916,45 @@ checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" [[package]] name = "wasi" -version = "0.14.2+wasi-0.2.4" +version = "0.14.4+wasi-0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9683f9a5a998d873c0d21fcbe3c083009670149a8fab228644b8bd36b2c48cb3" +checksum = "88a5f4a424faf49c3c2c344f166f0662341d470ea185e939657aaff130f0ec4a" dependencies = [ - "wit-bindgen-rt", + "wit-bindgen", ] [[package]] name = "wasm-bindgen" -version = "0.2.100" +version = "0.2.103" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1edc8929d7499fc4e8f0be2262a241556cfc54a0bea223790e71446f2aab1ef5" +checksum = "ab10a69fbd0a177f5f649ad4d8d3305499c42bab9aef2f7ff592d0ec8f833819" dependencies = [ "cfg-if", "once_cell", "rustversion", "wasm-bindgen-macro", + "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-backend" -version = "0.2.100" +version = "0.2.103" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2f0a0651a5c2bc21487bde11ee802ccaf4c51935d0d3d42a6101f98161700bc6" +checksum = "0bb702423545a6007bbc368fde243ba47ca275e549c8a28617f56f6ba53b1d1c" dependencies = [ "bumpalo", "log", "proc-macro2", "quote", - "syn 2.0.105", + "syn 2.0.106", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-futures" -version = "0.4.50" +version = "0.4.53" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "555d470ec0bc3bb57890405e5d4322cc9ea83cebb085523ced7be4144dac1e61" +checksum = "a0b221ff421256839509adbb55998214a70d829d3a28c69b4a6672e9d2a42f67" dependencies = [ "cfg-if", "js-sys", @@ -7895,9 +7965,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.100" +version = "0.2.103" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7fe63fc6d09ed3792bd0897b314f53de8e16568c2b3f7982f468c0bf9bd0b407" +checksum = "fc65f4f411d91494355917b605e1480033152658d71f722a90647f56a70c88a0" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -7905,22 +7975,22 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.100" +version = "0.2.103" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ae87ea40c9f689fc23f209965b6fb8a99ad69aeeb0231408be24920604395de" +checksum = "ffc003a991398a8ee604a401e194b6b3a39677b3173d6e74495eb51b82e99a32" dependencies = [ "proc-macro2", "quote", - "syn 2.0.105", + "syn 2.0.106", "wasm-bindgen-backend", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-shared" -version = "0.2.100" +version = "0.2.103" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a05d73b933a847d6cccdda8f838a22ff101ad9bf93e33684f39c1f5f0eece3d" +checksum = "293c37f4efa430ca14db3721dfbe48d8c33308096bd44d80ebaa775ab71ba1cf" dependencies = [ "unicode-ident", ] @@ -7946,7 +8016,7 @@ checksum = "673a33c33048a5ade91a6b139580fa174e19fb0d23f396dca9fa15f2e1e49b35" dependencies = [ "cc", "downcast-rs", - "rustix 1.0.8", + "rustix 1.1.2", "scoped-tls", "smallvec", "wayland-sys", @@ -7958,8 +8028,8 @@ version = "0.31.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c66a47e840dc20793f2264eb4b3e4ecb4b75d91c0dd4af04b456128e0bdd449d" dependencies = [ - "bitflags 2.9.1", - "rustix 1.0.8", + "bitflags 2.9.4", + "rustix 1.1.2", "wayland-backend", "wayland-scanner", ] @@ -7970,7 +8040,7 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "625c5029dbd43d25e6aa9615e88b829a5cad13b2819c4ae129fdbb7c31ab4c7e" dependencies = [ - "bitflags 2.9.1", + "bitflags 2.9.4", "cursor-icon", "wayland-backend", ] @@ -7981,7 +8051,7 @@ version = "0.31.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "447ccc440a881271b19e9989f75726d60faa09b95b0200a9b7eb5cc47c3eeb29" dependencies = [ - "rustix 1.0.8", + "rustix 1.1.2", "wayland-client", "xcursor", ] @@ -7992,7 +8062,7 @@ version = "0.32.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "efa790ed75fbfd71283bd2521a1cfdc022aabcc28bdcff00851f9e4ae88d9901" dependencies = [ - "bitflags 2.9.1", + "bitflags 2.9.4", "wayland-backend", "wayland-client", "wayland-scanner", @@ -8004,7 +8074,7 @@ version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a07a14257c077ab3279987c4f8bb987851bf57081b93710381daea94f2c2c032" dependencies = [ - "bitflags 2.9.1", + "bitflags 2.9.4", "wayland-backend", "wayland-client", "wayland-protocols", @@ -8017,7 +8087,7 @@ version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "efd94963ed43cf9938a090ca4f7da58eb55325ec8200c3848963e98dc25b78ec" dependencies = [ - "bitflags 2.9.1", + "bitflags 2.9.4", "wayland-backend", "wayland-client", "wayland-protocols", @@ -8049,9 +8119,9 @@ dependencies = [ [[package]] name = "web-sys" -version = "0.3.77" +version = "0.3.80" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "33b6dd2ef9186f1f2072e409e99cd22a975331a6b3591b12c764e0e55c60d5d2" +checksum = "fbe734895e869dc429d78c4b433f8d17d95f8d05317440b4fad5ab2d33e596dc" dependencies = [ "js-sys", "wasm-bindgen", @@ -8161,7 +8231,7 @@ checksum = "1d228f15bba3b9d56dde8bddbee66fa24545bd17b48d5128ccf4a8742b18e431" dependencies = [ "proc-macro2", "quote", - "syn 2.0.105", + "syn 2.0.106", ] [[package]] @@ -8170,7 +8240,7 @@ version = "0.38.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "36695906a1b53a3bf5c4289621efedac12b73eeb0b89e7e1a89b517302d5d75c" dependencies = [ - "thiserror 2.0.14", + "thiserror 2.0.16", "windows 0.61.3", "windows-core 0.61.2", ] @@ -8188,7 +8258,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6b0b3436f0729f6cdf2e6e9201f3d39dc95813fad61d826c1ed07918b4539353" dependencies = [ "arrayvec", - "bitflags 2.9.1", + "bitflags 2.9.4", "cfg_aliases", "document-features", "js-sys", @@ -8215,7 +8285,7 @@ checksum = "7f0aa306497a238d169b9dc70659105b4a096859a34894544ca81719242e1499" dependencies = [ "arrayvec", "bit-vec", - "bitflags 2.9.1", + "bitflags 2.9.4", "cfg_aliases", "document-features", "indexmap", @@ -8227,7 +8297,7 @@ dependencies = [ "raw-window-handle 0.6.2", "rustc-hash 1.1.0", "smallvec", - "thiserror 2.0.14", + "thiserror 2.0.16", "wgpu-hal", "wgpu-types", ] @@ -8242,7 +8312,7 @@ dependencies = [ "arrayvec", "ash", "bit-set", - "bitflags 2.9.1", + "bitflags 2.9.4", "block", "bytemuck", "cfg_aliases", @@ -8270,7 +8340,7 @@ dependencies = [ "renderdoc-sys", "rustc-hash 1.1.0", "smallvec", - "thiserror 2.0.14", + "thiserror 2.0.16", "wasm-bindgen", "web-sys", "wgpu-types", @@ -8284,7 +8354,7 @@ version = "24.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "50ac044c0e76c03a0378e7786ac505d010a873665e2d51383dcff8dd227dc69c" dependencies = [ - "bitflags 2.9.1", + "bitflags 2.9.4", "js-sys", "log", "web-sys", @@ -8308,11 +8378,11 @@ checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" [[package]] name = "winapi-util" -version = "0.1.9" +version = "0.1.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb" +checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22" dependencies = [ - "windows-sys 0.59.0", + "windows-sys 0.61.0", ] [[package]] @@ -8340,7 +8410,7 @@ dependencies = [ "windows-collections", "windows-core 0.61.2", "windows-future", - "windows-link", + "windows-link 0.1.3", "windows-numerics", ] @@ -8374,7 +8444,7 @@ checksum = "c0fdd3ddb90610c7638aa2b3a3ab2904fb9e5cdbecc643ddb3647212781c4ae3" dependencies = [ "windows-implement 0.60.0", "windows-interface 0.59.1", - "windows-link", + "windows-link 0.1.3", "windows-result 0.3.4", "windows-strings 0.4.2", ] @@ -8386,7 +8456,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fc6a41e98427b19fe4b73c550f060b59fa592d7d686537eebf9385621bfbad8e" dependencies = [ "windows-core 0.61.2", - "windows-link", + "windows-link 0.1.3", "windows-threading", ] @@ -8398,7 +8468,7 @@ checksum = "2bbd5b46c938e506ecbce286b6628a02171d56153ba733b6c741fc627ec9579b" dependencies = [ "proc-macro2", "quote", - "syn 2.0.105", + "syn 2.0.106", ] [[package]] @@ -8409,7 +8479,7 @@ checksum = "a47fddd13af08290e67f4acabf4b459f647552718f683a7b415d290ac744a836" dependencies = [ "proc-macro2", "quote", - "syn 2.0.105", + "syn 2.0.106", ] [[package]] @@ -8420,7 +8490,7 @@ checksum = "053c4c462dc91d3b1504c6fe5a726dd15e216ba718e84a0e46a88fbe5ded3515" dependencies = [ "proc-macro2", "quote", - "syn 2.0.105", + "syn 2.0.106", ] [[package]] @@ -8431,7 +8501,7 @@ checksum = "bd9211b69f8dcdfa817bfd14bf1c97c9188afa36f4750130fcdf3f400eca9fa8" dependencies = [ "proc-macro2", "quote", - "syn 2.0.105", + "syn 2.0.106", ] [[package]] @@ -8440,6 +8510,12 @@ version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5e6ad25900d524eaabdbbb96d20b4311e1e7ae1699af4fb28c17ae66c80d798a" +[[package]] +name = "windows-link" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "45e46c0661abb7180e7b9c281db115305d49ca1709ab8242adf09666d2173c65" + [[package]] name = "windows-numerics" version = "0.2.0" @@ -8447,7 +8523,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9150af68066c4c5c07ddc0ce30421554771e528bde427614c61038bc2c92c2b1" dependencies = [ "windows-core 0.61.2", - "windows-link", + "windows-link 0.1.3", ] [[package]] @@ -8456,7 +8532,7 @@ version = "0.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5b8a9ed28765efc97bbc954883f4e6796c33a06546ebafacbabee9696967499e" dependencies = [ - "windows-link", + "windows-link 0.1.3", "windows-result 0.3.4", "windows-strings 0.4.2", ] @@ -8476,7 +8552,7 @@ version = "0.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "56f42bd332cc6c8eac5af113fc0c1fd6a8fd2aa08a0119358686e5160d0586c6" dependencies = [ - "windows-link", + "windows-link 0.1.3", ] [[package]] @@ -8495,7 +8571,7 @@ version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "56e6c93f3a0c3b36176cb1327a4958a0353d5d166c2a35cb268ace15e91d3b57" dependencies = [ - "windows-link", + "windows-link 0.1.3", ] [[package]] @@ -8534,6 +8610,15 @@ dependencies = [ "windows-targets 0.53.3", ] +[[package]] +name = "windows-sys" +version = "0.61.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e201184e40b2ede64bc2ea34968b28e33622acdbbf37104f0e4a33f7abe657aa" +dependencies = [ + "windows-link 0.2.0", +] + [[package]] name = "windows-targets" version = "0.42.2" @@ -8549,21 +8634,6 @@ dependencies = [ "windows_x86_64_msvc 0.42.2", ] -[[package]] -name = "windows-targets" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" -dependencies = [ - "windows_aarch64_gnullvm 0.48.5", - "windows_aarch64_msvc 0.48.5", - "windows_i686_gnu 0.48.5", - "windows_i686_msvc 0.48.5", - "windows_x86_64_gnu 0.48.5", - "windows_x86_64_gnullvm 0.48.5", - "windows_x86_64_msvc 0.48.5", -] - [[package]] name = "windows-targets" version = "0.52.6" @@ -8586,7 +8656,7 @@ version = "0.53.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d5fe6031c4041849d7c496a8ded650796e7b6ecc19df1a431c1a363342e5dc91" dependencies = [ - "windows-link", + "windows-link 0.1.3", "windows_aarch64_gnullvm 0.53.0", "windows_aarch64_msvc 0.53.0", "windows_i686_gnu 0.53.0", @@ -8603,16 +8673,16 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b66463ad2e0ea3bbf808b7f1d371311c80e115c0b71d60efc142cafbcfb057a6" dependencies = [ - "windows-link", + "windows-link 0.1.3", ] [[package]] name = "windows-version" -version = "0.1.4" +version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e04a5c6627e310a23ad2358483286c7df260c964eb2d003d8efd6d0f4e79265c" +checksum = "69e061eb0a22b4a1d778ad70f7575ec7845490abb35b08fa320df7895882cacb" dependencies = [ - "windows-link", + "windows-link 0.2.0", ] [[package]] @@ -8621,12 +8691,6 @@ version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8" -[[package]] -name = "windows_aarch64_gnullvm" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" - [[package]] name = "windows_aarch64_gnullvm" version = "0.52.6" @@ -8645,12 +8709,6 @@ version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43" -[[package]] -name = "windows_aarch64_msvc" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" - [[package]] name = "windows_aarch64_msvc" version = "0.52.6" @@ -8669,12 +8727,6 @@ version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f" -[[package]] -name = "windows_i686_gnu" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" - [[package]] name = "windows_i686_gnu" version = "0.52.6" @@ -8705,12 +8757,6 @@ version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060" -[[package]] -name = "windows_i686_msvc" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" - [[package]] name = "windows_i686_msvc" version = "0.52.6" @@ -8729,12 +8775,6 @@ version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36" -[[package]] -name = "windows_x86_64_gnu" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" - [[package]] name = "windows_x86_64_gnu" version = "0.52.6" @@ -8753,12 +8793,6 @@ version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3" -[[package]] -name = "windows_x86_64_gnullvm" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" - [[package]] name = "windows_x86_64_gnullvm" version = "0.52.6" @@ -8777,12 +8811,6 @@ version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0" -[[package]] -name = "windows_x86_64_msvc" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" - [[package]] name = "windows_x86_64_msvc" version = "0.52.6" @@ -8804,7 +8832,7 @@ dependencies = [ "ahash", "android-activity", "atomic-waker", - "bitflags 2.9.1", + "bitflags 2.9.4", "block2 0.5.1", "bytemuck", "calloop", @@ -8858,21 +8886,18 @@ dependencies = [ [[package]] name = "winnow" -version = "0.7.12" +version = "0.7.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f3edebf492c8125044983378ecb5766203ad3b4c2f7a922bd7dd207f6d443e95" +checksum = "21a0236b59786fed61e2a80582dd500fe61f18b5dca67a4a067d0bc9039339cf" dependencies = [ "memchr", ] [[package]] -name = "wit-bindgen-rt" -version = "0.39.0" +name = "wit-bindgen" +version = "0.45.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6f42320e61fe2cfd34354ecb597f86f413484a798ba44a8ca1165c58d42da6c1" -dependencies = [ - "bitflags 2.9.1", -] +checksum = "5c573471f125075647d03df72e026074b7203790d41351cd6edc96f46bcccd36" [[package]] name = "write16" @@ -8918,7 +8943,7 @@ dependencies = [ "sha2", "soup3", "tao-macros", - "thiserror 2.0.14", + "thiserror 2.0.16", "url", "webkit2gtk", "webkit2gtk-sys", @@ -8951,24 +8976,24 @@ dependencies = [ [[package]] name = "x11rb" -version = "0.13.1" +version = "0.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5d91ffca73ee7f68ce055750bf9f6eca0780b8c85eff9bc046a3b0da41755e12" +checksum = "9993aa5be5a26815fe2c3eacfc1fde061fc1a1f094bf1ad2a18bf9c495dd7414" dependencies = [ "as-raw-xcb-connection", "gethostname", "libc", "libloading 0.8.8", "once_cell", - "rustix 0.38.44", + "rustix 1.1.2", "x11rb-protocol", ] [[package]] name = "x11rb-protocol" -version = "0.13.1" +version = "0.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec107c4503ea0b4a98ef47356329af139c0a4f7750e621cf2973cd3385ebcb3d" +checksum = "ea6fc2961e4ef194dcbfe56bb845534d0dc8098940c7e5c012a258bfec6701bd" [[package]] name = "xcursor" @@ -8992,7 +9017,7 @@ version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d039de8032a9a8856a6be89cea3e5d12fdd82306ab7c94d74e6deab2460651c5" dependencies = [ - "bitflags 2.9.1", + "bitflags 2.9.4", "dlib", "log", "once_cell", @@ -9058,7 +9083,7 @@ checksum = "2380878cad4ac9aac1e2435f3eb4020e8374b5f13c296cb75b4620ff8e229154" dependencies = [ "proc-macro2", "quote", - "syn 2.0.105", + "syn 2.0.106", "synstructure", ] @@ -9102,9 +9127,9 @@ dependencies = [ [[package]] name = "zbus" -version = "5.9.0" +version = "5.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4bb4f9a464286d42851d18a605f7193b8febaf5b0919d71c6399b7b26e5b0aad" +checksum = "2d07e46d035fb8e375b2ce63ba4e4ff90a7f73cf2ffb0138b29e1158d2eaadf7" dependencies = [ "async-broadcast", "async-recursion", @@ -9121,11 +9146,11 @@ dependencies = [ "tokio", "tracing", "uds_windows", - "windows-sys 0.59.0", - "winnow 0.7.12", - "zbus_macros 5.9.0", + "windows-sys 0.60.2", + "winnow 0.7.13", + "zbus_macros 5.11.0", "zbus_names 4.2.0", - "zvariant 5.6.0", + "zvariant 5.7.0", ] [[package]] @@ -9146,7 +9171,7 @@ checksum = "709ab20fc57cb22af85be7b360239563209258430bccf38d8b979c5a2ae3ecce" dependencies = [ "proc-macro2", "quote", - "syn 2.0.105", + "syn 2.0.106", "zbus-lockstep", "zbus_xml", "zvariant 4.2.0", @@ -9158,26 +9183,26 @@ version = "4.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "267db9407081e90bbfa46d841d3cbc60f59c0351838c4bc65199ecd79ab1983e" dependencies = [ - "proc-macro-crate 3.3.0", + "proc-macro-crate 3.4.0", "proc-macro2", "quote", - "syn 2.0.105", + "syn 2.0.106", "zvariant_utils 2.1.0", ] [[package]] name = "zbus_macros" -version = "5.9.0" +version = "5.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ef9859f68ee0c4ee2e8cde84737c78e3f4c54f946f2a38645d0d4c7a95327659" +checksum = "57e797a9c847ed3ccc5b6254e8bcce056494b375b511b3d6edcec0aeb4defaca" dependencies = [ - "proc-macro-crate 3.3.0", + "proc-macro-crate 3.4.0", "proc-macro2", "quote", - "syn 2.0.105", + "syn 2.0.106", "zbus_names 4.2.0", - "zvariant 5.6.0", - "zvariant_utils 3.2.0", + "zvariant 5.7.0", + "zvariant_utils 3.2.1", ] [[package]] @@ -9199,8 +9224,8 @@ checksum = "7be68e64bf6ce8db94f63e72f0c7eb9a60d733f7e0499e628dfab0f84d6bcb97" dependencies = [ "serde", "static_assertions", - "winnow 0.7.12", - "zvariant 5.6.0", + "winnow 0.7.13", + "zvariant 5.7.0", ] [[package]] @@ -9224,22 +9249,22 @@ checksum = "6df3dc4292935e51816d896edcd52aa30bc297907c26167fec31e2b0c6a32524" [[package]] name = "zerocopy" -version = "0.8.26" +version = "0.8.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1039dd0d3c310cf05de012d8a39ff557cb0d23087fd44cad61df08fc31907a2f" +checksum = "0894878a5fa3edfd6da3f88c4805f4c8558e2b996227a3d864f47fe11e38282c" dependencies = [ "zerocopy-derive", ] [[package]] name = "zerocopy-derive" -version = "0.8.26" +version = "0.8.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ecf5b4cc5364572d7f4c329661bcc82724222973f2cab6f050a4e5c22f75181" +checksum = "88d2b8d9c68ad2b9e4340d7832716a4d21a22a1154777ad56ea55c51a9cf3831" dependencies = [ "proc-macro2", "quote", - "syn 2.0.105", + "syn 2.0.106", ] [[package]] @@ -9259,7 +9284,7 @@ checksum = "d71e5d6e06ab090c67b5e44993ec16b72dcbaabc526db883a360057678b48502" dependencies = [ "proc-macro2", "quote", - "syn 2.0.105", + "syn 2.0.106", "synstructure", ] @@ -9288,7 +9313,7 @@ checksum = "6eafa6dfb17584ea3e2bd6e76e0cc15ad7af12b09abdd1ca55961bed9b1063c6" dependencies = [ "proc-macro2", "quote", - "syn 2.0.105", + "syn 2.0.106", ] [[package]] @@ -9299,9 +9324,9 @@ checksum = "3f423a2c17029964870cfaabb1f13dfab7d092a62a29a89264f4d36990ca414a" [[package]] name = "zune-jpeg" -version = "0.4.20" +version = "0.4.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fc1f7e205ce79eb2da3cd71c5f55f3589785cb7c79f6a03d1c8d1491bda5d089" +checksum = "29ce2c8a9384ad323cf564b67da86e21d3cfdff87908bc1223ed5c99bc792713" dependencies = [ "zune-core", ] @@ -9321,17 +9346,17 @@ dependencies = [ [[package]] name = "zvariant" -version = "5.6.0" +version = "5.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d91b3680bb339216abd84714172b5138a4edac677e641ef17e1d8cb1b3ca6e6f" +checksum = "999dd3be73c52b1fccd109a4a81e4fcd20fab1d3599c8121b38d04e1419498db" dependencies = [ "endi", "enumflags2", "serde", "url", - "winnow 0.7.12", - "zvariant_derive 5.6.0", - "zvariant_utils 3.2.0", + "winnow 0.7.13", + "zvariant_derive 5.7.0", + "zvariant_utils 3.2.1", ] [[package]] @@ -9340,24 +9365,24 @@ version = "4.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "73e2ba546bda683a90652bac4a279bc146adad1386f25379cf73200d2002c449" dependencies = [ - "proc-macro-crate 3.3.0", + "proc-macro-crate 3.4.0", "proc-macro2", "quote", - "syn 2.0.105", + "syn 2.0.106", "zvariant_utils 2.1.0", ] [[package]] name = "zvariant_derive" -version = "5.6.0" +version = "5.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3a8c68501be459a8dbfffbe5d792acdd23b4959940fc87785fb013b32edbc208" +checksum = "6643fd0b26a46d226bd90d3f07c1b5321fe9bb7f04673cb37ac6d6883885b68e" dependencies = [ - "proc-macro-crate 3.3.0", + "proc-macro-crate 3.4.0", "proc-macro2", "quote", - "syn 2.0.105", - "zvariant_utils 3.2.0", + "syn 2.0.106", + "zvariant_utils 3.2.1", ] [[package]] @@ -9368,19 +9393,18 @@ checksum = "c51bcff7cc3dbb5055396bcf774748c3dab426b4b8659046963523cee4808340" dependencies = [ "proc-macro2", "quote", - "syn 2.0.105", + "syn 2.0.106", ] [[package]] name = "zvariant_utils" -version = "3.2.0" +version = "3.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e16edfee43e5d7b553b77872d99bc36afdda75c223ca7ad5e3fbecd82ca5fc34" +checksum = "c6949d142f89f6916deca2232cf26a8afacf2b9fdc35ce766105e104478be599" dependencies = [ "proc-macro2", "quote", "serde", - "static_assertions", - "syn 2.0.105", - "winnow 0.7.12", + "syn 2.0.106", + "winnow 0.7.13", ] diff --git a/preview/src/components/date_picker/component.rs b/preview/src/components/date_picker/component.rs new file mode 100644 index 00000000..cd0c1299 --- /dev/null +++ b/preview/src/components/date_picker/component.rs @@ -0,0 +1,92 @@ +use crate::components::calendar::component::*; +use dioxus::prelude::*; + +use dioxus_primitives::{ + calendar::CalendarProps, + date_picker::{self, DatePickerInputProps, DatePickerProps, DatePickerTriggerProps}, +}; + +use time::UtcDateTime; + +#[component] +pub fn DatePicker(props: DatePickerProps) -> Element { + rsx! { + document::Link { + rel: "stylesheet", + href: asset!("/src/components/date_picker/style.css"), + } + div { + date_picker::DatePicker { + class: "date-picker", + value: props.value, + on_value_change: props.on_value_change, + selected_date: props.selected_date, + disabled: props.disabled, + read_only: props.read_only, + separator: props.separator, + on_format_placeholder: props.on_format_placeholder, + attributes: props.attributes, + {props.children} + } + } + } +} + +#[component] +pub fn DatePickerInput(props: DatePickerInputProps) -> Element { + rsx! { + date_picker::DatePickerInput { + class: "date-picker-input", + attributes: props.attributes, + {props.children} + } + } +} + +#[component] +pub fn DatePickerTrigger(props: DatePickerTriggerProps) -> Element { + rsx! { + date_picker::DatePickerTrigger { + class: "date-picker-trigger", + attributes: props.attributes, + {props.children} + } + } +} + +#[component] +pub fn DatePickerCalendar(props: CalendarProps) -> Element { + let mut view_date = use_signal(|| UtcDateTime::now().date()); + + use_effect(move || { + if let Some(date) = (props.selected_date)() { + view_date.set(date); + } + }); + + rsx! { + Calendar { + selected_date: props.selected_date, + on_date_change: props.on_date_change, + on_format_weekday: props.on_format_weekday, + on_format_month: props.on_format_month, + view_date: view_date(), + today: props.today, + on_view_change: move |date| view_date.set(date), + disabled: props.disabled, + first_day_of_week: props.first_day_of_week, + min_date: props.min_date, + max_date: props.max_date, + attributes: props.attributes, + CalendarHeader { + CalendarNavigation { + CalendarPreviousMonthButton {} + CalendarSelectMonth {} + CalendarSelectYear {} + CalendarNextMonthButton {} + } + } + CalendarGrid {} + } + } +} diff --git a/preview/src/components/date_picker/variants/main/style.css b/preview/src/components/date_picker/style.css similarity index 55% rename from preview/src/components/date_picker/variants/main/style.css rename to preview/src/components/date_picker/style.css index ad0678f6..37af7149 100644 --- a/preview/src/components/date_picker/variants/main/style.css +++ b/preview/src/components/date_picker/style.css @@ -22,19 +22,16 @@ box-shadow: inset 0 0 0 1px var(--light, var(--primary-color-6)) var(--dark, var(--primary-color-7)); color: var(--secondary-color-4); - cursor: text; gap: 0.25rem; transition: background-color 100ms ease-out; } .date-picker-trigger { - position: absolute; - right: 0; border: none; background-color: transparent; cursor: pointer; - padding: 0.25rem; - padding: 8px 12px; + position: relative; + margin-left: -35px; } .date-picker-input input::placeholder { @@ -68,58 +65,6 @@ outline: none; } -.date-picker-calendar { - position: absolute; - z-index: 1000; - top: 100%; - left: 0; - min-width: 100%; - box-sizing: border-box; - padding: 0.25rem; - border-radius: 0.5rem; - margin-top: 0.25rem; - background: var(--light, var(--primary-color)) - var(--dark, var(--primary-color-5)); - box-shadow: inset 0 0 0 1px var(--light, var(--primary-color-6)) - var(--dark, var(--primary-color-7)); - transform-origin: top; - opacity: 0; - pointer-events: none; - will-change: transform, opacity; -} - -.date-picker-calendar[data-state="closed"] { - pointer-events: none; - animation: date-picker-calendar-animate-out 150ms ease-in forwards; -} - -@keyframes date-picker-calendar-animate-out { - 0% { - opacity: 1; - transform: scale(1) translateY(0); - } - 100% { - opacity: 0; - transform: scale(0.95) translateY(-2px); - } -} - -.date-picker-calendar[data-state="open"] { - pointer-events: auto; - animation: date-picker-calendar-animate-in 150ms ease-out forwards; -} - -@keyframes date-picker-calendar-animate-in { - 0% { - opacity: 0; - transform: scale(0.95) translateY(-2px); - } - 100% { - opacity: 1; - transform: scale(1) translateY(0); - } -} - [data-disabled="true"] { cursor: not-allowed; opacity: 0.5; diff --git a/preview/src/components/date_picker/variants/main/mod.rs b/preview/src/components/date_picker/variants/main/mod.rs index ee471318..5b833405 100644 --- a/preview/src/components/date_picker/variants/main/mod.rs +++ b/preview/src/components/date_picker/variants/main/mod.rs @@ -1,39 +1,40 @@ +use super::super::component::*; use dioxus::prelude::*; -use dioxus_primitives::date_picker::{ - DatePicker, DatePickerCalendar, DatePickerInput, DatePickerTrigger, DatePickerValue, -}; + +use dioxus_i18n::tid; +use dioxus_primitives::date_picker::DatePickerValue; + +use time::{Date, Month, Weekday}; #[component] pub fn Demo() -> Element { - let mut value = use_signal(|| None::); + let v = DatePickerValue::new_day(None); + let mut value = use_signal(|| v); + + let mut selected_date = use_signal(|| None::); rsx! { - document::Link { - rel: "stylesheet", - href: asset!("/src/components/date_picker/variants/main/style.css"), - } div { DatePicker { - class: "date-picker", value: value(), - on_value_change: move |date| { - tracing::info!("Selected date: {:?}", date); - value.set(date); + selected_date: selected_date(), + on_value_change: move |v| { + tracing::info!("Selected: {v}"); + value.set(v); + selected_date.set(v.date()); }, + on_format_placeholder: || tid!("YMD"), DatePickerInput { - class: "date-picker-input", DatePickerTrigger { - class: "date-picker-trigger", - aria_label: "DatePicker Trigger", - svg { - class: "date-picker-expand-icon", - view_box: "0 0 24 24", - xmlns: "http://www.w3.org/2000/svg", - polyline { points: "6 9 12 15 18 9" } + aria_label: "DatePicker Trigger", + DatePickerCalendar { + selected_date: selected_date(), + on_date_change: move |date| selected_date.set(date), + on_format_weekday: |weekday: Weekday| tid!(&weekday.to_string()), + on_format_month: |month: Month| tid!(&month.to_string()), + } } } - } - DatePickerCalendar { class: "date-picker-calendar" } } } } diff --git a/preview/src/i18n/de-DE.ftl b/preview/src/i18n/de-DE.ftl index cea45f72..ec11174e 100644 --- a/preview/src/i18n/de-DE.ftl +++ b/preview/src/i18n/de-DE.ftl @@ -19,4 +19,7 @@ Wednesday = Mi Thursday = Do Friday = Fr Saturday = Sa -Sunday = So \ No newline at end of file +Sunday = So + +## Date +YMD = JJJJ-MM-TT \ No newline at end of file diff --git a/preview/src/i18n/en-US.ftl b/preview/src/i18n/en-US.ftl index 198f3453..0e28c922 100644 --- a/preview/src/i18n/en-US.ftl +++ b/preview/src/i18n/en-US.ftl @@ -19,4 +19,7 @@ Wednesday = We Thursday = Th Friday = Fr Saturday = Sa -Sunday = Su \ No newline at end of file +Sunday = Su + +## Date +YMD = YYYY-MM-DD \ No newline at end of file diff --git a/preview/src/i18n/es-ES.ftl b/preview/src/i18n/es-ES.ftl index 67393350..27ad71a0 100644 --- a/preview/src/i18n/es-ES.ftl +++ b/preview/src/i18n/es-ES.ftl @@ -19,4 +19,7 @@ Wednesday = Mi Thursday = Ju Friday = Vi Saturday = Sa -Sunday = Do \ No newline at end of file +Sunday = Do + +## Date +YMD = YYYY-MM-DD \ No newline at end of file diff --git a/preview/src/i18n/fr-FR.ftl b/preview/src/i18n/fr-FR.ftl index 9d3e9417..3ac890e7 100644 --- a/preview/src/i18n/fr-FR.ftl +++ b/preview/src/i18n/fr-FR.ftl @@ -19,4 +19,7 @@ Wednesday = Me Thursday = Je Friday = Ve Saturday = Sa -Sunday = Di \ No newline at end of file +Sunday = Di + +## Date +YMD = AAAA-MM-JJ \ No newline at end of file diff --git a/primitives/src/calendar.rs b/primitives/src/calendar.rs index 359e981f..24349327 100644 --- a/primitives/src/calendar.rs +++ b/primitives/src/calendar.rs @@ -230,6 +230,7 @@ pub struct CalendarProps { pub on_format_month: Callback, /// The month being viewed + #[props(default = ReadOnlySignal::new(Signal::new(UtcDateTime::now().date())))] pub view_date: ReadOnlySignal, /// The current date (used for highlighting today) @@ -896,24 +897,22 @@ pub fn CalendarGrid(props: CalendarGridProps) -> Element { date = date.next_day().expect("invalid or out-of-range date"); } + let mut date = view_date; // Add days of the month let num_days_in_month = view_date.month().length(view_date.year()); for day in 1..=num_days_in_month { - grid.push( - view_date - .replace_day(day) - .expect("invalid or out-of-range date"), - ); + date = view_date + .replace_day(day) + .expect("invalid or out-of-range date"); + grid.push(date); } // Add empty cells to complete the grid (for a clean layout) let remainder = grid.len() % 7; if remainder > 0 { - if let Some(mut date) = next_month(view_date) { - for _ in 1..=(7 - remainder) { - grid.push(date); - date = date.next_day().expect("invalid or out-of-range date"); - } + for _ in 1..=(7 - remainder) { + date = date.next_day().expect("invalid or out-of-range date"); + grid.push(date); } } diff --git a/primitives/src/date_picker.rs b/primitives/src/date_picker.rs index 147ac73f..4a638356 100644 --- a/primitives/src/date_picker.rs +++ b/primitives/src/date_picker.rs @@ -1,85 +1,176 @@ //! Defines the [`DatePicker`] component and its subcomponents, which allowing users to enter or select a date value use crate::{ - calendar::{ - Calendar, CalendarGrid, CalendarHeader, CalendarNavigation, CalendarNextMonthButton, - CalendarPreviousMonthButton, CalendarSelectMonth, CalendarSelectYear, - }, - focus::{use_focus_provider, FocusState}, - use_animated_open, use_effect, use_id_or, use_unique_id, + dioxus_elements::input_data::MouseButton, + popover::{PopoverContent, PopoverRoot, PopoverTrigger}, + ContentAlign, }; use dioxus::prelude::*; -use time::{macros::format_description, Date, UtcDateTime}; +use time::{macros::format_description, Date}; /// The value of the [`DatePicker`] component. -#[derive(Debug, Clone, PartialEq)] -pub enum DatePickerValue { - /// A single value for the date picker - Single { - /// The selected date - date: Date, - }, - /// A dates range value for the date picker - Range { - /// The first range date - start: Date, - /// The last range date - end: Option, - }, +#[derive(Copy, Clone)] +pub struct DatePickerValue { + /// A dates range value or single day + is_range: bool, + /// Current date value + value: DateValue, } impl DatePickerValue { - fn set(&self, date: Date) -> Self { - match self { - DatePickerValue::Single { .. } => DatePickerValue::Single { date }, - DatePickerValue::Range { start, end } => match end { - Some(_) => DatePickerValue::Range { + /// Create a single day value + pub fn new_day(date: Option) -> Self { + let is_range = false; + + match date { + Some(date) => Self { + is_range, + value: DateValue::Single { date }, + }, + None => Self::new_empty(is_range), + } + } + + /// Create new date range value + pub fn new_range(date: Option) -> Self { + let is_range = true; + + match date { + Some(date) => Self { + is_range, + value: DateValue::Range { start: date, end: None, }, - None => { - if date < *start { - DatePickerValue::Range { - start: date, - end: Some(*start), - } - } else { - DatePickerValue::Range { - start: *start, - end: Some(date), - } + }, + None => Self::new_empty(is_range), + } + } + + /// Create full date range value + pub fn range(start: Date, end: Date) -> Self { + let value = if end < start { + DateValue::Range { + start: end, + end: Some(start), + } + } else { + DateValue::Range { + start, + end: Some(end), + } + }; + Self { + is_range: true, + value, + } + } + + fn new(is_range: bool, date: Option) -> Self { + if is_range { + Self::new_range(date) + } else { + Self::new_day(date) + } + } + + fn new_empty(is_range: bool) -> Self { + Self { + is_range, + value: DateValue::Empty, + } + } + + fn part_count(&self) -> usize { + if self.is_range { + 2 + } else { + 1 + } + } + + fn set_date(&self, date: Option) -> Self { + match self.value { + DateValue::Range { start, end } => { + if end.is_some() { + Self::new_range(date) + } else { + match date { + Some(end) => Self::range(start, end), + None => *self, } } - }, + } + _ => Self::new(self.is_range, date), } } - fn date(&self) -> Date { - match self { - DatePickerValue::Single { date } => *date, - DatePickerValue::Range { start, end } => { - if let Some(date) = end { - return *date; + /// Return current selected date + pub fn date(&self) -> Option { + match self.value { + DateValue::Single { date } => Some(date), + DateValue::Range { start, end } => { + if end.is_some() { + return end; } - *start + Some(start) } + DateValue::Empty => None, + } + } + + // Returns `true` if the given date is selected + fn is_date_selected(&self, date: Option) -> bool { + self.date() == date + } + + fn ready_to_close(&self) -> bool { + match self.value { + DateValue::Range { end, .. } => end.is_some(), + _ => true, } } } impl std::fmt::Display for DatePickerValue { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{}", self.value) + } +} + +/// The value type of the [`DatePicker`] component. +#[derive(Debug, Copy, Clone, PartialEq)] +pub enum DateValue { + /// A single value for the date picker + Single { + /// The selected date + date: Date, + }, + /// A dates range value for the date picker + Range { + /// The first range date + start: Date, + /// The last range date + end: Option, + }, + /// None value for the date picker + Empty, +} + +impl std::fmt::Display for DateValue { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { - DatePickerValue::Single { date } => write!(f, "{date}"), - DatePickerValue::Range { start, end } => { + DateValue::Single { date } => write!(f, "{date}"), + DateValue::Range { start, end } => { let end_str = match end { - Some(value) => value.to_string(), + Some(date) => date.to_string(), None => String::default(), }; write!(f, "{start} - {end_str}") } + DateValue::Empty => write!(f, ""), } } } @@ -88,28 +179,30 @@ impl std::fmt::Display for DatePickerValue { #[derive(Copy, Clone)] struct DatePickerContext { // State - value: ReadOnlySignal>, - on_value_change: Callback>, + value: ReadOnlySignal, + on_value_change: Callback, + selected_date: ReadOnlySignal>, open: Signal, - focus_state: FocusState, + read_only: ReadOnlySignal, // Configuration disabled: ReadOnlySignal, - calendar_id: Signal>, + separator: &'static str, + format_placeholder: Callback<(), String>, } impl DatePickerContext { fn set_date(&mut self, date: Option) { - match date { - Some(date) => match (self.value)() { - Some(value) => self.on_value_change.call(Some(value.set(date))), - None => { - tracing::error!("create new"); - self.on_value_change - .call(Some(DatePickerValue::Single { date })) - } - }, - None => self.on_value_change.call(None), + let value = (self.value)(); + if value.is_date_selected(date) { + return; + } + + let value = value.set_date(date); + self.on_value_change.call(value); + + if value.ready_to_close() { + self.open.set(false); } } } @@ -118,30 +211,38 @@ impl DatePickerContext { #[derive(Props, Clone, PartialEq)] pub struct DatePickerProps { /// The controlled value of the date picker - pub value: ReadOnlySignal>, + pub value: ReadOnlySignal, /// Callback when value changes #[props(default)] - pub on_value_change: Callback>, + pub on_value_change: Callback, /// The selected date #[props(default)] - pub selected_date: Signal>, - - /// Whether focus should loop around when reaching the end - #[props(default = ReadOnlySignal::new(Signal::new(true)))] - pub roving_loop: ReadOnlySignal, + pub selected_date: ReadOnlySignal>, /// Whether the date picker is disabled #[props(default)] pub disabled: ReadOnlySignal, + /// Whether the date picker is enable user input + #[props(default = ReadOnlySignal::new(Signal::new(false)))] + pub read_only: ReadOnlySignal, + + /// Separator between range value + #[props(default = " - ")] + pub separator: &'static str, + + /// Callback when display placeholder + #[props(default = Callback::new(|_| "YYYY-MM-DD".to_string()))] + pub on_format_placeholder: Callback<(), String>, + /// Additional attributes to extend the date picker element #[props(extends = GlobalAttributes)] - attributes: Vec, + pub attributes: Vec, /// The children of the date picker element - children: Element, + pub children: Element, } /// # DatePicker @@ -159,17 +260,17 @@ pub struct DatePickerProps { #[component] pub fn DatePicker(props: DatePickerProps) -> Element { let open = use_signal(|| false); - let calendar_id = use_signal(|| None); - let focus_state = use_focus_provider(props.roving_loop); // Create context provider for child components use_context_provider(|| DatePickerContext { open, - calendar_id, - focus_state, value: props.value, on_value_change: props.on_value_change, + selected_date: props.selected_date, disabled: props.disabled, + read_only: props.read_only, + separator: props.separator, + format_placeholder: props.on_format_placeholder, }); rsx! { @@ -188,44 +289,46 @@ pub fn DatePicker(props: DatePickerProps) -> Element { pub struct DatePickerTriggerProps { /// Additional attributes for the trigger button #[props(extends = GlobalAttributes)] - attributes: Vec, + pub attributes: Vec, /// The children to render inside the trigger - children: Element, + pub children: Element, } /// # DatePickerTrigger /// -/// The trigger button for the [`DatePicker`](super::date_picker::DatePicker) component which controls if the [`DatePickerCalendar`](super::date_picker::DatePickerCalendar) is rendered. -/// -/// This must be used inside a [`DatePicker`](super::date_picker::DatePicker) component. +/// The `PopoverTrigger` is a button that toggles the visibility of the [`PopoverContent`]. /// /// ```rust /// ``` #[component] pub fn DatePickerTrigger(props: DatePickerTriggerProps) -> Element { - let ctx = use_context::(); + let mut ctx = use_context::(); let mut open = ctx.open; - rsx! { - button { - // Standard HTML attributes - disabled: (ctx.disabled)(), - - onclick: move |_| { - open.toggle(); - }, - - // ARIA attributes - aria_haspopup: "calendarbox", - aria_expanded: open(), - aria_controls: ctx.calendar_id, - - // Pass through other attributes - ..props.attributes, + use_effect(move || { + let date = (ctx.selected_date)(); + ctx.set_date(date); + }); - // Render children (options) - {props.children} + rsx! { + PopoverRoot { + class: "popover", + open: open(), + on_open_change: move |v| open.set(v), + PopoverTrigger { attributes: props.attributes, + svg { + class: "date-picker-expand-icon", + view_box: "0 0 24 24", + xmlns: "http://www.w3.org/2000/svg", + polyline { points: "6 9 12 15 18 9" } + } + } + PopoverContent { + gap: "0.25rem", + align: ContentAlign::End, + {props.children} + } } } } @@ -235,10 +338,10 @@ pub fn DatePickerTrigger(props: DatePickerTriggerProps) -> Element { pub struct DatePickerInputProps { /// Additional attributes for the value element #[props(extends = GlobalAttributes)] - attributes: Vec, + pub attributes: Vec, /// The children of the date picker element - children: Element, + pub children: Element, } /// # DatePickerInput @@ -251,152 +354,50 @@ pub struct DatePickerInputProps { pub fn DatePickerInput(props: DatePickerInputProps) -> Element { let mut ctx = use_context::(); - let display_value = use_memo(move || match (ctx.value)() { - Some(value) => value.to_string(), - None => String::default(), - }); - - rsx! { - input { - placeholder: "YYYY-MM-DD", - value: display_value, - oninput: move |e| { - let text = e.value().parse().unwrap_or(display_value()); - - let format = format_description!("[year]-[month]-[day]"); - if let Ok(date) = Date::parse(&text, &format) { - ctx.set_date(Some(date)); - } - }, - ..props.attributes, - } - {props.children} - } -} - -/// The props for the [`DatePickerCalendar`] component -#[derive(Props, Clone, PartialEq)] -pub struct DatePickerCalendarProps { - /// The ID of the calendar for ARIA attributes - #[props(default)] - pub id: ReadOnlySignal>, + let display_value = use_memo(move || ctx.value.to_string()); - /// Additional attributes for the list - #[props(extends = GlobalAttributes)] - attributes: Vec, -} - -/// # DatePickerCalendar -/// -/// The Calendar popover for the [`DatePicker`](super::date_picker::DatePicker). -/// The calendar will only be rendered when the DatePicker is open. -/// -/// This must be used inside a [`DatePicker`](super::date_picker::DatePicker). -/// -/// ## Example -/// -/// ```rust -/// ``` -#[component] -pub fn DatePickerCalendar(props: DatePickerCalendarProps) -> Element { - let mut ctx = use_context::(); - - let id = use_unique_id(); - let id = use_id_or(id, props.id); - use_effect(move || { - ctx.calendar_id.set(Some(id())); - }); + let placeholder = { + let capacity = (ctx.value)().part_count(); + let text = ctx.format_placeholder.call(()); + vec![text; capacity].join(ctx.separator) + }; - let mut open = ctx.open; - let mut calendarbox_ref: Signal>> = use_signal(|| None); - let focused = move || open() && !ctx.focus_state.any_focused(); + let handle_input = move |e: Event| { + let text = e.value().parse().unwrap_or(display_value()); - use_effect(move || { - let Some(calendarbox_ref) = calendarbox_ref() else { - return; - }; - if focused() { - spawn(async move { - _ = calendarbox_ref.set_focus(true); - }); - } - }); + let value = (ctx.value)(); + let format = format_description!("[year]-[month]-[day]"); - let onkeydown = move |event: KeyboardEvent| { - let key = event.key(); + if value.is_range { + } else { + if text.is_empty() { + ctx.set_date(None); + return; + } - if key == Key::Escape { - open.set(false); - event.prevent_default(); - event.stop_propagation(); + let date = Date::parse(&text, &format).ok(); + if date.is_some_and(|_| !value.is_date_selected(date)) { + ctx.set_date(date); + } } }; - let render = use_animated_open(id, open); - let render = use_memo(render); - - let mut selected_date = use_signal(|| None::); - let mut view_date = use_signal(|| UtcDateTime::now().date()); - - use_effect(move || match (ctx.value)() { - Some(value) => { - let date = value.date(); - view_date.set(date); - selected_date.set(Some(date)); - } - None => selected_date.set(None), - }); - rsx! { - if render() { - div { - id, - role: "calendarbox", - tabindex: if focused() { "0" } else { "-1" }, - - // Data attributes - "data-state": if open() { "open" } else { "closed" }, - - onmounted: move |evt| calendarbox_ref.set(Some(evt.data())), - onkeydown, - - ..props.attributes, - - Calendar { - selected_date: selected_date(), - on_date_change: move |date| { - ctx.set_date(date); - open.set(false); - }, - view_date: view_date(), - on_view_change: move |new_view: Date| { - view_date.set(new_view); - }, - CalendarHeader { - CalendarNavigation { - CalendarPreviousMonthButton { - svg { - class: "calendar-previous-month-icon", - view_box: "0 0 24 24", - xmlns: "http://www.w3.org/2000/svg", - polyline { points: "15 6 9 12 15 18" } - } - } - CalendarSelectMonth { class: "calendar-month-select" } - CalendarSelectYear { class: "calendar-year-select" } - CalendarNextMonthButton { - svg { - class: "calendar-next-month-icon", - view_box: "0 0 24 24", - xmlns: "http://www.w3.org/2000/svg", - polyline { points: "9 18 15 12 9 6" } - } - } - } - } - CalendarGrid {} + input { + style: "min-width: 240px", + placeholder, + value: display_value, + disabled: ctx.disabled, + readonly: ctx.read_only, + cursor: if (ctx.read_only)() { "pointer" } else { "text" }, + oninput: handle_input, + onpointerdown: move |event| { + if (ctx.read_only)() && event.trigger_button() == Some(MouseButton::Primary) { + ctx.open.toggle(); } - } + }, + ..props.attributes, } + {props.children} } } From e13704088302a1bcea8958cb2ab4236f2554bfb3 Mon Sep 17 00:00:00 2001 From: Dmitry Date: Fri, 3 Oct 2025 21:45:09 +0300 Subject: [PATCH 4/7] [*] DatePicker: component cli --- Cargo.lock | 34 +++++++++++++++++++ component.json | 3 +- .../src/components/date_picker/component.json | 13 +++++++ .../src/components/date_picker/component.rs | 7 ++-- preview/src/components/date_picker/mod.rs | 2 ++ primitives/src/calendar.rs | 4 +-- primitives/src/date_picker.rs | 18 +++++----- 7 files changed, 64 insertions(+), 17 deletions(-) create mode 100644 preview/src/components/date_picker/component.json create mode 100644 preview/src/components/date_picker/mod.rs diff --git a/Cargo.lock b/Cargo.lock index 04e1d4bb..40a2fdbf 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1618,6 +1618,18 @@ dependencies = [ "syn 2.0.106", ] +[[package]] +name = "dioxus-core-macro" +version = "0.7.0-rc.0" +source = "git+https://github.com/DioxusLabs/dioxus#eef4db67b1164934eb29b7ac4d4bcd693bba25a5" +dependencies = [ + "convert_case 0.8.0", + "dioxus-rsx 0.7.0-rc.0 (git+https://github.com/DioxusLabs/dioxus)", + "proc-macro2", + "quote", + "syn 2.0.106", +] + [[package]] name = "dioxus-core-types" version = "0.7.0-rc.0" @@ -2052,6 +2064,17 @@ dependencies = [ "syn 2.0.106", ] +[[package]] +name = "dioxus-rsx" +version = "0.7.0-rc.0" +source = "git+https://github.com/DioxusLabs/dioxus#eef4db67b1164934eb29b7ac4d4bcd693bba25a5" +dependencies = [ + "proc-macro2", + "proc-macro2-diagnostics", + "quote", + "syn 2.0.106", +] + [[package]] name = "dioxus-server" version = "0.7.0-rc.0" @@ -4222,6 +4245,17 @@ dependencies = [ "syn 2.0.106", ] +[[package]] +name = "match_token" +version = "0.35.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac84fd3f360fcc43dc5f5d186f02a94192761a080e8bc58621ad4d12296a58cf" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.106", +] + [[package]] name = "matchers" version = "0.2.0" diff --git a/component.json b/component.json index 8984b2b3..628343eb 100644 --- a/component.json +++ b/component.json @@ -32,6 +32,7 @@ "preview/src/components/toggle_group", "preview/src/components/context_menu", "preview/src/components/aspect_ratio", - "preview/src/components/scroll_area" + "preview/src/components/scroll_area", + "preview/src/components/date_picker" ] } diff --git a/preview/src/components/date_picker/component.json b/preview/src/components/date_picker/component.json new file mode 100644 index 00000000..583cd28f --- /dev/null +++ b/preview/src/components/date_picker/component.json @@ -0,0 +1,13 @@ +{ + "name": "date_picker", + "description": "A date picker component to select or input dates.", + "authors": ["Evan Almloff"], + "exclude": ["variants", "docs.md", "component.json"], + "cargoDependencies": [ + { + "name": "dioxus-primitives", + "git": "https://github.com/DioxusLabs/components" + } + ], + "globalAssets": ["../../../assets/dx-components-theme.css"] +} diff --git a/preview/src/components/date_picker/component.rs b/preview/src/components/date_picker/component.rs index cd0c1299..159a2126 100644 --- a/preview/src/components/date_picker/component.rs +++ b/preview/src/components/date_picker/component.rs @@ -1,4 +1,3 @@ -use crate::components::calendar::component::*; use dioxus::prelude::*; use dioxus_primitives::{ @@ -6,15 +5,13 @@ use dioxus_primitives::{ date_picker::{self, DatePickerInputProps, DatePickerProps, DatePickerTriggerProps}, }; +use crate::components::calendar::component::*; use time::UtcDateTime; #[component] pub fn DatePicker(props: DatePickerProps) -> Element { rsx! { - document::Link { - rel: "stylesheet", - href: asset!("/src/components/date_picker/style.css"), - } + document::Link { rel: "stylesheet", href: asset!("./style.css"), } div { date_picker::DatePicker { class: "date-picker", diff --git a/preview/src/components/date_picker/mod.rs b/preview/src/components/date_picker/mod.rs new file mode 100644 index 00000000..9a8ae556 --- /dev/null +++ b/preview/src/components/date_picker/mod.rs @@ -0,0 +1,2 @@ +mod component; +pub use component::*; \ No newline at end of file diff --git a/primitives/src/calendar.rs b/primitives/src/calendar.rs index 9027dadb..ddb51ba1 100644 --- a/primitives/src/calendar.rs +++ b/primitives/src/calendar.rs @@ -230,8 +230,8 @@ pub struct CalendarProps { pub on_format_month: Callback, /// The month being viewed - #[props(default = ReadOnlySignal::new(Signal::new(UtcDateTime::now().date())))] - pub view_date: ReadOnlySignal, + #[props(default = ReadSignal::new(Signal::new(UtcDateTime::now().date())))] + pub view_date: ReadSignal, /// The current date (used for highlighting today) #[props(default = UtcDateTime::now().date())] diff --git a/primitives/src/date_picker.rs b/primitives/src/date_picker.rs index 4a638356..daf42690 100644 --- a/primitives/src/date_picker.rs +++ b/primitives/src/date_picker.rs @@ -179,14 +179,14 @@ impl std::fmt::Display for DateValue { #[derive(Copy, Clone)] struct DatePickerContext { // State - value: ReadOnlySignal, + value: ReadSignal, on_value_change: Callback, - selected_date: ReadOnlySignal>, + selected_date: ReadSignal>, open: Signal, - read_only: ReadOnlySignal, + read_only: ReadSignal, // Configuration - disabled: ReadOnlySignal, + disabled: ReadSignal, separator: &'static str, format_placeholder: Callback<(), String>, } @@ -211,7 +211,7 @@ impl DatePickerContext { #[derive(Props, Clone, PartialEq)] pub struct DatePickerProps { /// The controlled value of the date picker - pub value: ReadOnlySignal, + pub value: ReadSignal, /// Callback when value changes #[props(default)] @@ -219,15 +219,15 @@ pub struct DatePickerProps { /// The selected date #[props(default)] - pub selected_date: ReadOnlySignal>, + pub selected_date: ReadSignal>, /// Whether the date picker is disabled #[props(default)] - pub disabled: ReadOnlySignal, + pub disabled: ReadSignal, /// Whether the date picker is enable user input - #[props(default = ReadOnlySignal::new(Signal::new(false)))] - pub read_only: ReadOnlySignal, + #[props(default = ReadSignal::new(Signal::new(false)))] + pub read_only: ReadSignal, /// Separator between range value #[props(default = " - ")] From cf54b21552d65613baaa7f113700bd0524056967 Mon Sep 17 00:00:00 2001 From: Dmitry Date: Sat, 4 Oct 2025 17:24:41 +0300 Subject: [PATCH 5/7] [*] DatePicker: implement DateSegment Element - Refact signals - Split Popover component - Delete unused styles --- Cargo.lock | 1 + .../src/components/date_picker/component.rs | 49 +- preview/src/components/date_picker/style.css | 42 +- .../date_picker/variants/main/mod.rs | 20 +- preview/src/i18n/de-DE.ftl | 4 +- preview/src/i18n/en-US.ftl | 4 +- preview/src/i18n/es-ES.ftl | 4 +- preview/src/i18n/fr-FR.ftl | 4 +- primitives/Cargo.toml | 1 + primitives/src/date_picker.rs | 490 +++++++++++------- 10 files changed, 406 insertions(+), 213 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 40a2fdbf..a14c1e49 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2014,6 +2014,7 @@ dependencies = [ "dioxus", "dioxus-time", "lazy-js-bundle 0.6.2", + "num-integer", "time", "tracing", ] diff --git a/preview/src/components/date_picker/component.rs b/preview/src/components/date_picker/component.rs index 159a2126..b044fdf2 100644 --- a/preview/src/components/date_picker/component.rs +++ b/preview/src/components/date_picker/component.rs @@ -2,7 +2,11 @@ use dioxus::prelude::*; use dioxus_primitives::{ calendar::CalendarProps, - date_picker::{self, DatePickerInputProps, DatePickerProps, DatePickerTriggerProps}, + date_picker::{self, DatePickerInputProps, DatePickerProps}, + popover::{ + PopoverContent, PopoverContentProps, PopoverRootProps, PopoverTrigger, PopoverTriggerProps, + }, + ContentAlign, }; use crate::components::calendar::component::*; @@ -11,7 +15,7 @@ use time::UtcDateTime; #[component] pub fn DatePicker(props: DatePickerProps) -> Element { rsx! { - document::Link { rel: "stylesheet", href: asset!("./style.css"), } + document::Link { rel: "stylesheet", href: asset!("./style.css") } div { date_picker::DatePicker { class: "date-picker", @@ -20,8 +24,6 @@ pub fn DatePicker(props: DatePickerProps) -> Element { selected_date: props.selected_date, disabled: props.disabled, read_only: props.read_only, - separator: props.separator, - on_format_placeholder: props.on_format_placeholder, attributes: props.attributes, {props.children} } @@ -34,6 +36,9 @@ pub fn DatePickerInput(props: DatePickerInputProps) -> Element { rsx! { date_picker::DatePickerInput { class: "date-picker-input", + on_format_day_placeholder: props.on_format_day_placeholder, + on_format_month_placeholder: props.on_format_month_placeholder, + on_format_year_placeholder: props.on_format_year_placeholder, attributes: props.attributes, {props.children} } @@ -41,11 +46,43 @@ pub fn DatePickerInput(props: DatePickerInputProps) -> Element { } #[component] -pub fn DatePickerTrigger(props: DatePickerTriggerProps) -> Element { +pub fn DatePickerPopover(props: PopoverRootProps) -> Element { rsx! { - date_picker::DatePickerTrigger { + date_picker::DatePickerPopover { + class: "popover", + is_modal: props.is_modal, + default_open: props.default_open, + attributes: props.attributes, + {props.children} + } + } +} + +#[component] +pub fn DatePickerPopoverTrigger(props: PopoverTriggerProps) -> Element { + rsx! { + PopoverTrigger { class: "date-picker-trigger", attributes: props.attributes, + svg { + class: "date-picker-expand-icon", + view_box: "0 0 24 24", + xmlns: "http://www.w3.org/2000/svg", + polyline { points: "6 9 12 15 18 9" } + } + } + } +} + +#[component] +pub fn DatePickerPopoverContent(props: PopoverContentProps) -> Element { + rsx! { + PopoverContent { + class: "popover-content", + id: props.id, + side: props.side, + align: ContentAlign::End, + attributes: props.attributes, {props.children} } } diff --git a/preview/src/components/date_picker/style.css b/preview/src/components/date_picker/style.css index 37af7149..955cd16f 100644 --- a/preview/src/components/date_picker/style.css +++ b/preview/src/components/date_picker/style.css @@ -34,7 +34,7 @@ margin-left: -35px; } -.date-picker-input input::placeholder { +.date-picker-input { color: var(--secondary-color-5); } @@ -69,3 +69,43 @@ cursor: not-allowed; opacity: 0.5; } + +.date-picker-group { + align-items: center; + width: fit-content; + display: flex; +} + +.date-picker-container { + display: inline; + box-sizing: border-box; + flex-direction: row; + align-items: center; + justify-content: space-between; + padding: 0.25rem; + padding: 8px 40px 8px 12px; + border: none; + border-radius: 0.5rem; + border-radius: calc(0.5rem); + background: none; + background: var(--light, var(--primary-color)) var(--dark, var(--primary-color-3)); + box-shadow: inset 0 0 0 1px var(--light, var(--primary-color-6)) var(--dark, var(--primary-color-7)); + color: var(--secondary-color-4); + gap: 0.25rem; + transition: background-color 100ms ease-out; + width: fit-content; + min-width: 150px; +} + +.date-segment { + padding: 0 2px; + caret-color: transparent; +} + +.date-segment[no-date="true"] { + color: var(--secondary-color-5); +} + +.date-segment[is-separator="true"] { + padding: 0; +} \ No newline at end of file diff --git a/preview/src/components/date_picker/variants/main/mod.rs b/preview/src/components/date_picker/variants/main/mod.rs index 5b833405..7cb9cbfb 100644 --- a/preview/src/components/date_picker/variants/main/mod.rs +++ b/preview/src/components/date_picker/variants/main/mod.rs @@ -23,15 +23,19 @@ pub fn Demo() -> Element { value.set(v); selected_date.set(v.date()); }, - on_format_placeholder: || tid!("YMD"), DatePickerInput { - DatePickerTrigger { - aria_label: "DatePicker Trigger", - DatePickerCalendar { - selected_date: selected_date(), - on_date_change: move |date| selected_date.set(date), - on_format_weekday: |weekday: Weekday| tid!(&weekday.to_string()), - on_format_month: |month: Month| tid!(&month.to_string()), + on_format_day_placeholder: || tid!("D_Abbr"), + on_format_month_placeholder: || tid!("M_Abbr"), + on_format_year_placeholder: || tid!("Y_Abbr"), + DatePickerPopover { + DatePickerPopoverTrigger {} + DatePickerPopoverContent { + DatePickerCalendar { + selected_date: selected_date(), + on_date_change: move |date| selected_date.set(date), + on_format_weekday: |weekday: Weekday| tid!(& weekday.to_string()), + on_format_month: |month: Month| tid!(& month.to_string()), + } } } } diff --git a/preview/src/i18n/de-DE.ftl b/preview/src/i18n/de-DE.ftl index ec11174e..78f6c06a 100644 --- a/preview/src/i18n/de-DE.ftl +++ b/preview/src/i18n/de-DE.ftl @@ -22,4 +22,6 @@ Saturday = Sa Sunday = So ## Date -YMD = JJJJ-MM-TT \ No newline at end of file +D_Abbr = T +M_Abbr = M +Y_Abbr = J \ No newline at end of file diff --git a/preview/src/i18n/en-US.ftl b/preview/src/i18n/en-US.ftl index 0e28c922..a9f96f35 100644 --- a/preview/src/i18n/en-US.ftl +++ b/preview/src/i18n/en-US.ftl @@ -22,4 +22,6 @@ Saturday = Sa Sunday = Su ## Date -YMD = YYYY-MM-DD \ No newline at end of file +D_Abbr = D +M_Abbr = M +Y_Abbr = Y \ No newline at end of file diff --git a/preview/src/i18n/es-ES.ftl b/preview/src/i18n/es-ES.ftl index 27ad71a0..13e48c71 100644 --- a/preview/src/i18n/es-ES.ftl +++ b/preview/src/i18n/es-ES.ftl @@ -22,4 +22,6 @@ Saturday = Sa Sunday = Do ## Date -YMD = YYYY-MM-DD \ No newline at end of file +D_Abbr = D +M_Abbr = M +Y_Abbr = Y \ No newline at end of file diff --git a/preview/src/i18n/fr-FR.ftl b/preview/src/i18n/fr-FR.ftl index 3ac890e7..7b040633 100644 --- a/preview/src/i18n/fr-FR.ftl +++ b/preview/src/i18n/fr-FR.ftl @@ -22,4 +22,6 @@ Saturday = Sa Sunday = Di ## Date -YMD = AAAA-MM-JJ \ No newline at end of file +D_Abbr = J +M_Abbr = M +Y_Abbr = A \ No newline at end of file diff --git a/primitives/Cargo.toml b/primitives/Cargo.toml index 05a3b07e..836358e6 100644 --- a/primitives/Cargo.toml +++ b/primitives/Cargo.toml @@ -15,6 +15,7 @@ repository = "https://github.com/DioxusLabs/components" dioxus.workspace = true dioxus-time = { git = "https://github.com/ealmloff/dioxus-std", branch = "0.7" } time = { version = "0.3.41", features = ["std", "macros", "parsing"] } +num-integer = "0.1.46" tracing.workspace = true [build-dependencies] diff --git a/primitives/src/date_picker.rs b/primitives/src/date_picker.rs index daf42690..4e3fa284 100644 --- a/primitives/src/date_picker.rs +++ b/primitives/src/date_picker.rs @@ -1,19 +1,20 @@ //! Defines the [`DatePicker`] component and its subcomponents, which allowing users to enter or select a date value use crate::{ - dioxus_elements::input_data::MouseButton, - popover::{PopoverContent, PopoverRoot, PopoverTrigger}, - ContentAlign, + focus::{use_focus_controlled_item, use_focus_provider, FocusState}, + popover::{PopoverRoot, PopoverRootProps}, + use_unique_id, }; use dioxus::prelude::*; -use time::{macros::format_description, Date}; +use num_integer::Integer; +use std::{fmt::Display, str::FromStr}; +use time::{Date, Month, UtcDateTime}; /// The value of the [`DatePicker`] component. +/// Currently this can only be a single date, but support for ranges is planned. #[derive(Copy, Clone)] pub struct DatePickerValue { - /// A dates range value or single day - is_range: bool, /// Current date value value: DateValue, } @@ -21,88 +22,13 @@ pub struct DatePickerValue { impl DatePickerValue { /// Create a single day value pub fn new_day(date: Option) -> Self { - let is_range = false; - match date { Some(date) => Self { - is_range, value: DateValue::Single { date }, }, - None => Self::new_empty(is_range), - } - } - - /// Create new date range value - pub fn new_range(date: Option) -> Self { - let is_range = true; - - match date { - Some(date) => Self { - is_range, - value: DateValue::Range { - start: date, - end: None, - }, + None => Self { + value: DateValue::Empty, }, - None => Self::new_empty(is_range), - } - } - - /// Create full date range value - pub fn range(start: Date, end: Date) -> Self { - let value = if end < start { - DateValue::Range { - start: end, - end: Some(start), - } - } else { - DateValue::Range { - start, - end: Some(end), - } - }; - Self { - is_range: true, - value, - } - } - - fn new(is_range: bool, date: Option) -> Self { - if is_range { - Self::new_range(date) - } else { - Self::new_day(date) - } - } - - fn new_empty(is_range: bool) -> Self { - Self { - is_range, - value: DateValue::Empty, - } - } - - fn part_count(&self) -> usize { - if self.is_range { - 2 - } else { - 1 - } - } - - fn set_date(&self, date: Option) -> Self { - match self.value { - DateValue::Range { start, end } => { - if end.is_some() { - Self::new_range(date) - } else { - match date { - Some(end) => Self::range(start, end), - None => *self, - } - } - } - _ => Self::new(self.is_range, date), } } @@ -110,13 +36,6 @@ impl DatePickerValue { pub fn date(&self) -> Option { match self.value { DateValue::Single { date } => Some(date), - DateValue::Range { start, end } => { - if end.is_some() { - return end; - } - - Some(start) - } DateValue::Empty => None, } } @@ -125,13 +44,6 @@ impl DatePickerValue { fn is_date_selected(&self, date: Option) -> bool { self.date() == date } - - fn ready_to_close(&self) -> bool { - match self.value { - DateValue::Range { end, .. } => end.is_some(), - _ => true, - } - } } impl std::fmt::Display for DatePickerValue { @@ -148,13 +60,6 @@ pub enum DateValue { /// The selected date date: Date, }, - /// A dates range value for the date picker - Range { - /// The first range date - start: Date, - /// The last range date - end: Option, - }, /// None value for the date picker Empty, } @@ -163,13 +68,6 @@ impl std::fmt::Display for DateValue { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { DateValue::Single { date } => write!(f, "{date}"), - DateValue::Range { start, end } => { - let end_str = match end { - Some(date) => date.to_string(), - None => String::default(), - }; - write!(f, "{start} - {end_str}") - } DateValue::Empty => write!(f, ""), } } @@ -187,8 +85,7 @@ struct DatePickerContext { // Configuration disabled: ReadSignal, - separator: &'static str, - format_placeholder: Callback<(), String>, + focus: FocusState, } impl DatePickerContext { @@ -198,12 +95,10 @@ impl DatePickerContext { return; } - let value = value.set_date(date); + let value = DatePickerValue::new_day(date); self.on_value_change.call(value); - if value.ready_to_close() { - self.open.set(false); - } + self.open.set(false); } } @@ -229,13 +124,9 @@ pub struct DatePickerProps { #[props(default = ReadSignal::new(Signal::new(false)))] pub read_only: ReadSignal, - /// Separator between range value - #[props(default = " - ")] - pub separator: &'static str, - - /// Callback when display placeholder - #[props(default = Callback::new(|_| "YYYY-MM-DD".to_string()))] - pub on_format_placeholder: Callback<(), String>, + /// Whether focus should loop around when reaching the end. + #[props(default = ReadSignal::new(Signal::new(false)))] + pub roving_loop: ReadSignal, /// Additional attributes to extend the date picker element #[props(extends = GlobalAttributes)] @@ -260,17 +151,17 @@ pub struct DatePickerProps { #[component] pub fn DatePicker(props: DatePickerProps) -> Element { let open = use_signal(|| false); + let focus = use_focus_provider(props.roving_loop); // Create context provider for child components use_context_provider(|| DatePickerContext { - open, value: props.value, on_value_change: props.on_value_change, selected_date: props.selected_date, - disabled: props.disabled, + open, read_only: props.read_only, - separator: props.separator, - format_placeholder: props.on_format_placeholder, + disabled: props.disabled, + focus, }); rsx! { @@ -284,51 +175,212 @@ pub fn DatePicker(props: DatePickerProps) -> Element { } } -/// The props for the [`SelectDateTrigger`] component -#[derive(Props, Clone, PartialEq)] -pub struct DatePickerTriggerProps { - /// Additional attributes for the trigger button - #[props(extends = GlobalAttributes)] - pub attributes: Vec, - - /// The children to render inside the trigger - pub children: Element, -} - -/// # DatePickerTrigger +/// # DatePickerPopover /// -/// The `PopoverTrigger` is a button that toggles the visibility of the [`PopoverContent`]. +/// The `DatePickerPopover` is a button that toggles the visibility of the [`PopoverContent`]. /// /// ```rust /// ``` #[component] -pub fn DatePickerTrigger(props: DatePickerTriggerProps) -> Element { - let mut ctx = use_context::(); +pub fn DatePickerPopover(props: PopoverRootProps) -> Element { + let ctx = use_context::(); let mut open = ctx.open; - use_effect(move || { - let date = (ctx.selected_date)(); - ctx.set_date(date); - }); - rsx! { PopoverRoot { - class: "popover", open: open(), on_open_change: move |v| open.set(v), - PopoverTrigger { attributes: props.attributes, - svg { - class: "date-picker-expand-icon", - view_box: "0 0 24 24", - xmlns: "http://www.w3.org/2000/svg", - polyline { points: "6 9 12 15 18 9" } + attributes: props.attributes, + {props.children} + } + } +} + +// The props for the [`DateSegment`] component +#[derive(Props, Clone, PartialEq)] +struct DateSegmentProps { + // The index of the segment + pub index: ReadSignal, + + // The controlled value of the date picker + pub value: ReadSignal>, + + // Default value + pub default: T, + + // Callback when value changes + #[props(default)] + pub on_value_change: Callback>, + + // The minimum value + pub min: T, + + // The maximum value + pub max: T, + + // Max field length + pub max_length: usize, + + // Callback when display placeholder + pub on_format_placeholder: Callback<(), String>, + + // Additional attributes for the value element + #[props(extends = GlobalAttributes)] + pub attributes: Vec, +} + +#[component] +fn DateSegment( + props: DateSegmentProps, +) -> Element { + let mut text_value = use_signal(|| "".to_string()); + use_effect(move || { + if let Some(value) = (props.value)() { + text_value.set(value.to_string()); + } + }); + + // The formatted text for the segment + let display_value = use_memo(move || { + let value = (props.value)(); + match value { + Some(value) => format!("{:0>width$}", value, width = props.max_length), + None => props + .on_format_placeholder + .call(()) + .repeat(props.max_length), + } + }); + + let mut ctx = use_context::(); + + let mut set_value = move |text: String| { + if text.is_empty() { + props.on_value_change.call(None); + ctx.focus.focus_prev(); + return; + } + + let value = text.parse::().ok(); + if let Some(value) = value { + let inRange = value >= props.min && value <= props.max; + + let newValue = (text + "0").parse::().unwrap_or(value); + if inRange && newValue > props.max { + ctx.focus.focus_next(); + } + }; + + props.on_value_change.call(value); + }; + + let clamp_value = move |value: T| { + if value < props.min { + props.max + } else if value > props.max { + props.min + } else { + value + } + }; + + let handle_keydown = move |event: Event| { + let key = event.key(); + match key { + Key::Character(actual_char) => { + if actual_char.parse::().is_ok() { + let mut text = text_value(); + if text.len() == props.max_length { + text = String::default(); + }; + text.push_str(&actual_char); + set_value(text); } + + event.prevent_default(); + event.stop_propagation(); + } + Key::Backspace => { + let mut text = text_value(); + text.pop(); + set_value(text); } - PopoverContent { - gap: "0.25rem", - align: ContentAlign::End, - {props.children} + Key::Delete => { + let mut text = text_value(); + text.remove(0); + set_value(text); } + Key::ArrowLeft => { + ctx.focus.focus_prev(); + } + Key::ArrowRight => { + ctx.focus.focus_next(); + } + Key::ArrowUp => { + let value = match (props.value)() { + Some(mut value) => { + value.inc(); + clamp_value(value) + } + None => props.default, + }; + props.on_value_change.call(Some(value)); + } + Key::ArrowDown => { + let value = match (props.value)() { + Some(mut value) => { + value.dec(); + clamp_value(value) + } + None => props.default, + }; + props.on_value_change.call(Some(value)); + } + _ => (), + } + }; + + let focused = move || ctx.focus.is_focused(props.index.cloned()); + let onmounted = use_focus_controlled_item(props.index); + + rsx! { + span { + class: "date-segment", + id: use_unique_id(), + role: "spinbutton", + aria_valuemin: props.min.to_string(), + aria_valuemax: props.max.to_string(), + aria_valuenow: display_value, + inputmode: "numeric", + contenteditable: !(ctx.read_only)(), + spellcheck: false, + tabindex: if focused() { "0" } else { "-1" }, + enterkeyhint: "next", + onkeydown: handle_keydown, + onmounted, + onfocus: move |_| { + ctx.focus.set_focus(Some(props.index.cloned())); + if (ctx.open)() { + ctx.open.set(false); + } + }, + "no-date": (props.value)().is_none(), + "data-disabled": (ctx.disabled)(), + {display_value} + } + } +} + +#[component] +fn DateSeparator() -> Element { + rsx! { + span { + class: "date-segment", + aria_hidden: "true", + tabindex: "-1", + "is-separator": true, + "no-date": true, + {"-"} } } } @@ -336,6 +388,18 @@ pub fn DatePickerTrigger(props: DatePickerTriggerProps) -> Element { /// The props for the [`DatePickerInput`] component #[derive(Props, Clone, PartialEq)] pub struct DatePickerInputProps { + /// Callback when display day placeholder + #[props(default = Callback::new(|_| "D".to_string()))] + pub on_format_day_placeholder: Callback<(), String>, + + /// Callback when display month placeholder + #[props(default = Callback::new(|_| "M".to_string()))] + pub on_format_month_placeholder: Callback<(), String>, + + /// Callback when display year placeholder + #[props(default = Callback::new(|_| "Y".to_string()))] + pub on_format_year_placeholder: Callback<(), String>, + /// Additional attributes for the value element #[props(extends = GlobalAttributes)] pub attributes: Vec, @@ -354,50 +418,88 @@ pub struct DatePickerInputProps { pub fn DatePickerInput(props: DatePickerInputProps) -> Element { let mut ctx = use_context::(); - let display_value = use_memo(move || ctx.value.to_string()); + let mut day_value = use_signal(|| None); + let mut month_value = use_signal(|| None); + let mut year_value = use_signal(|| None); - let placeholder = { - let capacity = (ctx.value)().part_count(); - let text = ctx.format_placeholder.call(()); - vec![text; capacity].join(ctx.separator) - }; + use_effect(move || { + let date = (ctx.selected_date)(); + year_value.set(date.map(|d| d.year())); + month_value.set(date.map(|d| d.month() as u8)); + day_value.set(date.map(|d| d.day())); + }); - let handle_input = move |e: Event| { - let text = e.value().parse().unwrap_or(display_value()); + use_effect(move || { + let year = match year_value() { + Some(value) => value, + None => return, + }; - let value = (ctx.value)(); - let format = format_description!("[year]-[month]-[day]"); + let month = match month_value() { + Some(value) => match Month::try_from(value) { + Ok(m) => m, + Err(..) => return, + }, + None => return, + }; - if value.is_range { - } else { - if text.is_empty() { - ctx.set_date(None); - return; + let day = match day_value() { + Some(value) => { + let max = month.length(year); + value.clamp(1, max) } + None => return, + }; - let date = Date::parse(&text, &format).ok(); - if date.is_some_and(|_| !value.is_date_selected(date)) { - ctx.set_date(date); - } - } - }; + let date = Date::from_calendar_date(year, month, day).ok(); + tracing::info!("Parsed new date {date:?}"); + ctx.set_date(date); + }); + + let today = UtcDateTime::now().date(); rsx! { - input { - style: "min-width: 240px", - placeholder, - value: display_value, - disabled: ctx.disabled, - readonly: ctx.read_only, - cursor: if (ctx.read_only)() { "pointer" } else { "text" }, - oninput: handle_input, - onpointerdown: move |event| { - if (ctx.read_only)() && event.trigger_button() == Some(MouseButton::Primary) { - ctx.open.toggle(); + div { + class: "date-picker-group", + div { + class: "date-picker-container", + DateSegment { + aria_label: "year", + index: 0usize, + value: year_value, + default: today.year(), + on_value_change: move |value: Option| year_value.set(value), + min: 1, + max: 9999, + max_length: 4, + on_format_placeholder: props.on_format_year_placeholder, } - }, - ..props.attributes, + DateSeparator {} + DateSegment { + aria_label: "month", + index: 1usize, + value: month_value, + default: today.month() as u8, + on_value_change: move |value: Option| month_value.set(value), + min: Month::January as u8, + max: Month::December as u8, + max_length: 2, + on_format_placeholder: props.on_format_month_placeholder, + } + DateSeparator {} + DateSegment { + aria_label: "day", + index: 2usize, + value: day_value, + default: today.day(), + on_value_change: move |value: Option| day_value.set(value), + min: 1, + max: 31, + max_length: 2, + on_format_placeholder: props.on_format_day_placeholder, + } + } + {props.children} } - {props.children} } } From 0d4e8871555b61327559e4cbe6bd7ff9858630a8 Mon Sep 17 00:00:00 2001 From: Dmitry Date: Tue, 14 Oct 2025 12:52:13 +0300 Subject: [PATCH 6/7] [*] DatePicker: docs --- .../src/components/date_picker/component.rs | 27 +- preview/src/components/date_picker/docs.md | 30 ++ .../date_picker/variants/main/mod.rs | 7 +- primitives/src/date_picker.rs | 367 ++++++++++++++++-- 4 files changed, 378 insertions(+), 53 deletions(-) diff --git a/preview/src/components/date_picker/component.rs b/preview/src/components/date_picker/component.rs index b044fdf2..d9b5dc37 100644 --- a/preview/src/components/date_picker/component.rs +++ b/preview/src/components/date_picker/component.rs @@ -3,14 +3,10 @@ use dioxus::prelude::*; use dioxus_primitives::{ calendar::CalendarProps, date_picker::{self, DatePickerInputProps, DatePickerProps}, - popover::{ - PopoverContent, PopoverContentProps, PopoverRootProps, PopoverTrigger, PopoverTriggerProps, - }, - ContentAlign, + popover::{PopoverContentProps, PopoverRootProps, PopoverTriggerProps}, }; use crate::components::calendar::component::*; -use time::UtcDateTime; #[component] pub fn DatePicker(props: DatePickerProps) -> Element { @@ -61,7 +57,7 @@ pub fn DatePickerPopover(props: PopoverRootProps) -> Element { #[component] pub fn DatePickerPopoverTrigger(props: PopoverTriggerProps) -> Element { rsx! { - PopoverTrigger { + date_picker::DatePickerPopoverTrigger { class: "date-picker-trigger", attributes: props.attributes, svg { @@ -77,11 +73,11 @@ pub fn DatePickerPopoverTrigger(props: PopoverTriggerProps) -> Element { #[component] pub fn DatePickerPopoverContent(props: PopoverContentProps) -> Element { rsx! { - PopoverContent { + date_picker::DatePickerPopoverContent { class: "popover-content", id: props.id, side: props.side, - align: ContentAlign::End, + align: props.align, attributes: props.attributes, {props.children} } @@ -90,23 +86,16 @@ pub fn DatePickerPopoverContent(props: PopoverContentProps) -> Element { #[component] pub fn DatePickerCalendar(props: CalendarProps) -> Element { - let mut view_date = use_signal(|| UtcDateTime::now().date()); - - use_effect(move || { - if let Some(date) = (props.selected_date)() { - view_date.set(date); - } - }); - rsx! { - Calendar { + date_picker::DatePickerCalendar { + class: "calendar", selected_date: props.selected_date, on_date_change: props.on_date_change, on_format_weekday: props.on_format_weekday, on_format_month: props.on_format_month, - view_date: view_date(), + view_date: props.view_date, today: props.today, - on_view_change: move |date| view_date.set(date), + on_view_change: props.on_view_change, disabled: props.disabled, first_day_of_week: props.first_day_of_week, min_date: props.min_date, diff --git a/preview/src/components/date_picker/docs.md b/preview/src/components/date_picker/docs.md index dfbe0232..e674f9a7 100644 --- a/preview/src/components/date_picker/docs.md +++ b/preview/src/components/date_picker/docs.md @@ -3,4 +3,34 @@ The DatePicker component is used to display a date input and a Calendar popover, ## Component Structure ```rust +DatePicker { + // The currently value in the date picker (type DatePickerValue). + value, + // The currently selected date in the date picker (if any). + selected_date, + on_value_change: move |v: DatePickerValue| { + // This callback is triggered when a date is selected in the + // calendar or the user entered it from the keyboard. + // The date parameter contains the selected date. + }, + // Allows the user to enter a date using the keyboard. + // The input field should contain a button to display the calendar and the calendar itself. + DatePickerInput { + // The DatePickerPopover is the root popover component that contains the trigger and Calendar. + DatePickerPopover { + // The DatePickerPopoverTrigger contains the elements that will trigger the popover + // to display Calendar when clicked. + DatePickerPopoverTrigger {} + // The DatePickerPopoverContent contains the Calendar that will be displayed when + // the user clicks on the trigger. + DatePickerPopoverContent { + // The alignment of the DatePickerPopoverContent relative to the DatePickerPopoverTrigger. + // Can be one of Start, Center, or End. Recommended use End for default value. + align: ContentAlign::End, + // Customized Calendar components + DatePickerCalendar {} + } + } + } +} ``` \ No newline at end of file diff --git a/preview/src/components/date_picker/variants/main/mod.rs b/preview/src/components/date_picker/variants/main/mod.rs index 7cb9cbfb..0051e5ca 100644 --- a/preview/src/components/date_picker/variants/main/mod.rs +++ b/preview/src/components/date_picker/variants/main/mod.rs @@ -2,7 +2,7 @@ use super::super::component::*; use dioxus::prelude::*; use dioxus_i18n::tid; -use dioxus_primitives::date_picker::DatePickerValue; +use dioxus_primitives::{date_picker::DatePickerValue, ContentAlign}; use time::{Date, Month, Weekday}; @@ -19,7 +19,7 @@ pub fn Demo() -> Element { value: value(), selected_date: selected_date(), on_value_change: move |v| { - tracing::info!("Selected: {v}"); + tracing::info!("Date changed to: {v}"); value.set(v); selected_date.set(v.date()); }, @@ -27,9 +27,10 @@ pub fn Demo() -> Element { on_format_day_placeholder: || tid!("D_Abbr"), on_format_month_placeholder: || tid!("M_Abbr"), on_format_year_placeholder: || tid!("Y_Abbr"), - DatePickerPopover { + DatePickerPopover { DatePickerPopoverTrigger {} DatePickerPopoverContent { + align: ContentAlign::End, DatePickerCalendar { selected_date: selected_date(), on_date_change: move |date| selected_date.set(date), diff --git a/primitives/src/date_picker.rs b/primitives/src/date_picker.rs index 4e3fa284..2add700c 100644 --- a/primitives/src/date_picker.rs +++ b/primitives/src/date_picker.rs @@ -1,15 +1,16 @@ //! Defines the [`DatePicker`] component and its subcomponents, which allowing users to enter or select a date value use crate::{ + calendar::{Calendar, CalendarProps}, focus::{use_focus_controlled_item, use_focus_provider, FocusState}, - popover::{PopoverRoot, PopoverRootProps}, + popover::*, use_unique_id, }; use dioxus::prelude::*; use num_integer::Integer; use std::{fmt::Display, str::FromStr}; -use time::{Date, Month, UtcDateTime}; +use time::{macros::date, Date, Month, UtcDateTime}; /// The value of the [`DatePicker`] component. /// Currently this can only be a single date, but support for ranges is planned. @@ -86,6 +87,8 @@ struct DatePickerContext { // Configuration disabled: ReadSignal, focus: FocusState, + min_date: Date, + max_date: Date, } impl DatePickerContext { @@ -124,6 +127,14 @@ pub struct DatePickerProps { #[props(default = ReadSignal::new(Signal::new(false)))] pub read_only: ReadSignal, + /// Lower limit of the range of available dates + #[props(default = date!(1925-01-01))] + pub min_date: Date, + + /// Upper limit of the range of available dates + #[props(default = date!(2050-12-31))] + pub max_date: Date, + /// Whether focus should loop around when reaching the end. #[props(default = ReadSignal::new(Signal::new(false)))] pub roving_loop: ReadSignal, @@ -142,6 +153,40 @@ pub struct DatePickerProps { /// /// ## Example /// ```rust +/// use dioxus::prelude::*; +/// use dioxus_primitives::{date_picker::*, ContentAlign}; +/// use time::Date; +/// #[component] +/// pub fn Demo() -> Element { +/// let v = DatePickerValue::new_day(None); +/// let mut value = use_signal(|| v); +/// let mut selected_date = use_signal(|| None::); +/// rsx! { +/// div { +/// DatePicker { +/// value: value(), +/// selected_date: selected_date(), +/// on_value_change: move |v| { +/// tracing::info!("Date changed to: {v}"); +/// value.set(v); +/// selected_date.set(v.date()); +/// }, +/// DatePickerInput { +/// DatePickerPopover { +/// DatePickerPopoverTrigger {} +/// DatePickerPopoverContent { +/// align: ContentAlign::End, +/// DatePickerCalendar { +/// selected_date: selected_date(), +/// on_date_change: move |date| selected_date.set(date), +/// } +/// } +/// } +/// } +/// } +/// } +/// } +///} /// ``` /// /// # Styling @@ -162,6 +207,8 @@ pub fn DatePicker(props: DatePickerProps) -> Element { read_only: props.read_only, disabled: props.disabled, focus, + min_date: props.min_date, + max_date: props.max_date, }); rsx! { @@ -177,9 +224,44 @@ pub fn DatePicker(props: DatePickerProps) -> Element { /// # DatePickerPopover /// -/// The `DatePickerPopover` is a button that toggles the visibility of the [`PopoverContent`]. +/// The `DatePickerPopover` component wraps all the popover components and manages the state. /// +/// ## Example /// ```rust +/// use dioxus::prelude::*; +/// use dioxus_primitives::{date_picker::*, ContentAlign}; +/// use time::Date; +/// #[component] +/// pub fn Demo() -> Element { +/// let v = DatePickerValue::new_day(None); +/// let mut value = use_signal(|| v); +/// let mut selected_date = use_signal(|| None::); +/// rsx! { +/// div { +/// DatePicker { +/// value: value(), +/// selected_date: selected_date(), +/// on_value_change: move |v| { +/// tracing::info!("Date changed to: {v}"); +/// value.set(v); +/// selected_date.set(v.date()); +/// }, +/// DatePickerInput { +/// DatePickerPopover { +/// DatePickerPopoverTrigger {} +/// DatePickerPopoverContent { +/// align: ContentAlign::End, +/// DatePickerCalendar { +/// selected_date: selected_date(), +/// on_date_change: move |date| selected_date.set(date), +/// } +/// } +/// } +/// } +/// } +/// } +/// } +///} /// ``` #[component] pub fn DatePickerPopover(props: PopoverRootProps) -> Element { @@ -196,6 +278,189 @@ pub fn DatePickerPopover(props: PopoverRootProps) -> Element { } } +/// # DatePickerPopoverTrigger +/// +/// The `DatePickerPopoverTrigger` is a button that toggles the visibility of the [`DatePickerPopoverContent`]. +/// +/// This must be used inside a [`DatePickerPopover`] component. +/// +/// ## Example +/// ```rust +/// use dioxus::prelude::*; +/// use dioxus_primitives::{date_picker::*, ContentAlign}; +/// use time::Date; +/// #[component] +/// pub fn Demo() -> Element { +/// let v = DatePickerValue::new_day(None); +/// let mut value = use_signal(|| v); +/// let mut selected_date = use_signal(|| None::); +/// rsx! { +/// div { +/// DatePicker { +/// value: value(), +/// selected_date: selected_date(), +/// on_value_change: move |v| { +/// tracing::info!("Date changed to: {v}"); +/// value.set(v); +/// selected_date.set(v.date()); +/// }, +/// DatePickerInput { +/// DatePickerPopover { +/// DatePickerPopoverTrigger {} +/// DatePickerPopoverContent { +/// align: ContentAlign::End, +/// DatePickerCalendar { +/// selected_date: selected_date(), +/// on_date_change: move |date| selected_date.set(date), +/// } +/// } +/// } +/// } +/// } +/// } +/// } +///} +/// ``` +#[component] +pub fn DatePickerPopoverTrigger(props: PopoverTriggerProps) -> Element { + rsx! { + PopoverTrigger { + aria_label: "Show Calendar", + attributes: props.attributes, + {props.children} + } + } +} + +/// # DatePickerPopoverContent +/// +/// The `DatePickerPopoverContent` component defines the content of the popover. This component will +/// only be rendered if the popover is open. +/// +/// This must be used inside a [`DatePickerPopover`] component. +/// +/// ## Example +/// ```rust +/// use dioxus::prelude::*; +/// use dioxus_primitives::{date_picker::*, ContentAlign}; +/// use time::Date; +/// #[component] +/// pub fn Demo() -> Element { +/// let v = DatePickerValue::new_day(None); +/// let mut value = use_signal(|| v); +/// let mut selected_date = use_signal(|| None::); +/// rsx! { +/// div { +/// DatePicker { +/// value: value(), +/// selected_date: selected_date(), +/// on_value_change: move |v| { +/// tracing::info!("Date changed to: {v}"); +/// value.set(v); +/// selected_date.set(v.date()); +/// }, +/// DatePickerInput { +/// DatePickerPopover { +/// DatePickerPopoverTrigger {} +/// DatePickerPopoverContent { +/// align: ContentAlign::End, +/// DatePickerCalendar { +/// selected_date: selected_date(), +/// on_date_change: move |date| selected_date.set(date), +/// } +/// } +/// } +/// } +/// } +/// } +/// } +///} +/// ``` +#[component] +pub fn DatePickerPopoverContent(props: PopoverContentProps) -> Element { + rsx! { + PopoverContent { + id: props.id, + side: props.side, + align: props.align, + attributes: props.attributes, + {props.children} + } + } +} + +/// # DatePickerCalendar +/// +/// The [`DatePickerCalendar`] component provides an accessible calendar interface with arrow key navigation, month switching, and date selection. +/// Used as date picker popover component +/// +/// ## Example +/// ```rust +/// use dioxus::prelude::*; +/// use dioxus_primitives::{date_picker::*, ContentAlign}; +/// use time::Date; +/// #[component] +/// pub fn Demo() -> Element { +/// let v = DatePickerValue::new_day(None); +/// let mut value = use_signal(|| v); +/// let mut selected_date = use_signal(|| None::); +/// rsx! { +/// div { +/// DatePicker { +/// value: value(), +/// selected_date: selected_date(), +/// on_value_change: move |v| { +/// tracing::info!("Date changed to: {v}"); +/// value.set(v); +/// selected_date.set(v.date()); +/// }, +/// DatePickerInput { +/// DatePickerPopover { +/// DatePickerPopoverTrigger {} +/// DatePickerPopoverContent { +/// align: ContentAlign::End, +/// DatePickerCalendar { +/// selected_date: selected_date(), +/// on_date_change: move |date| selected_date.set(date), +/// } +/// } +/// } +/// } +/// } +/// } +/// } +///} +/// ``` +#[component] +pub fn DatePickerCalendar(props: CalendarProps) -> Element { + let ctx = use_context::(); + let mut view_date = use_signal(|| UtcDateTime::now().date()); + + use_effect(move || { + if let Some(date) = (props.selected_date)() { + view_date.set(date); + } + }); + + rsx! { + Calendar { + selected_date: props.selected_date, + on_date_change: props.on_date_change, + on_format_weekday: props.on_format_weekday, + on_format_month: props.on_format_month, + view_date: view_date(), + today: props.today, + on_view_change: move |date| view_date.set(date), + disabled: props.disabled, + first_day_of_week: props.first_day_of_week, + min_date: ctx.min_date, + max_date: ctx.max_date, + attributes: props.attributes, + {props.children} + } + } +} + // The props for the [`DateSegment`] component #[derive(Props, Clone, PartialEq)] struct DateSegmentProps { @@ -235,9 +500,11 @@ fn DateSegment( ) -> Element { let mut text_value = use_signal(|| "".to_string()); use_effect(move || { - if let Some(value) = (props.value)() { - text_value.set(value.to_string()); - } + let text = match (props.value)() { + Some(value) => value.to_string(), + None => String::default(), + }; + text_value.set(text); }); // The formatted text for the segment @@ -252,6 +519,8 @@ fn DateSegment( } }); + let now_value = use_memo(move || (props.value)().unwrap_or(props.default)); + let mut ctx = use_context::(); let mut set_value = move |text: String| { @@ -343,14 +612,19 @@ fn DateSegment( let focused = move || ctx.focus.is_focused(props.index.cloned()); let onmounted = use_focus_controlled_item(props.index); + let span_id = use_unique_id(); + let id = use_memo(move || format!("span-{span_id}")); + let label_id = format!("{id}-label"); + rsx! { span { class: "date-segment", - id: use_unique_id(), + id, role: "spinbutton", aria_valuemin: props.min.to_string(), aria_valuemax: props.max.to_string(), - aria_valuenow: display_value, + aria_valuenow: now_value.to_string(), + aria_labelledby: "{label_id}", inputmode: "numeric", contenteditable: !(ctx.read_only)(), spellcheck: false, @@ -366,6 +640,7 @@ fn DateSegment( }, "no-date": (props.value)().is_none(), "data-disabled": (ctx.disabled)(), + ..props.attributes, {display_value} } } @@ -410,9 +685,44 @@ pub struct DatePickerInputProps { /// # DatePickerInput /// -/// The input element for the [`DatePicker`](super::date_picker::DatePicker) component which allow users to enter a date value. +/// The input element for the [`DatePicker`] component which allow users to enter a date value. /// +/// ## Example /// ```rust +/// use dioxus::prelude::*; +/// use dioxus_primitives::{date_picker::*, ContentAlign}; +/// use time::Date; +/// #[component] +/// pub fn Demo() -> Element { +/// let v = DatePickerValue::new_day(None); +/// let mut value = use_signal(|| v); +/// let mut selected_date = use_signal(|| None::); +/// rsx! { +/// div { +/// DatePicker { +/// value: value(), +/// selected_date: selected_date(), +/// on_value_change: move |v| { +/// tracing::info!("Date changed to: {v}"); +/// value.set(v); +/// selected_date.set(v.date()); +/// }, +/// DatePickerInput { +/// DatePickerPopover { +/// DatePickerPopoverTrigger {} +/// DatePickerPopoverContent { +/// align: ContentAlign::End, +/// DatePickerCalendar { +/// selected_date: selected_date(), +/// on_date_change: move |date| selected_date.set(date), +/// } +/// } +/// } +/// } +/// } +/// } +/// } +///} /// ``` #[component] pub fn DatePickerInput(props: DatePickerInputProps) -> Element { @@ -430,30 +740,25 @@ pub fn DatePickerInput(props: DatePickerInputProps) -> Element { }); use_effect(move || { - let year = match year_value() { - Some(value) => value, - None => return, - }; - - let month = match month_value() { - Some(value) => match Month::try_from(value) { - Ok(m) => m, - Err(..) => return, - }, - None => return, - }; - - let day = match day_value() { - Some(value) => { - let max = month.length(year); - value.clamp(1, max) + if let Some(year) = year_value() { + let value = month_value().unwrap_or(0); + if let Ok(month) = Month::try_from(value) { + if let Some(value) = day_value() { + let max = month.length(year); + let day = value.clamp(1, max); + + let date = Date::from_calendar_date(year, month, day) + .ok() + .map(|date| date.clamp(ctx.min_date, ctx.max_date)); + + tracing::info!("Parsed date: {date:?}"); + ctx.set_date(date); + return; + } } - None => return, - }; + } - let date = Date::from_calendar_date(year, month, day).ok(); - tracing::info!("Parsed new date {date:?}"); - ctx.set_date(date); + ctx.set_date(None); }); let today = UtcDateTime::now().date(); From 445beb80e94a52f7f3442017635275e5077bc1ee Mon Sep 17 00:00:00 2001 From: Dmitry Date: Wed, 15 Oct 2025 16:57:21 +0300 Subject: [PATCH 7/7] [*] DatePicker: fix user input after blur --- primitives/src/date_picker.rs | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/primitives/src/date_picker.rs b/primitives/src/date_picker.rs index 2add700c..fddcea40 100644 --- a/primitives/src/date_picker.rs +++ b/primitives/src/date_picker.rs @@ -507,6 +507,8 @@ fn DateSegment( text_value.set(text); }); + let mut reset_value = use_signal(|| false); + // The formatted text for the segment let display_value = use_memo(move || { let value = (props.value)(); @@ -530,13 +532,14 @@ fn DateSegment( return; } - let value = text.parse::().ok(); + let value = text.parse::().map(|v| v.min(props.max)).ok(); if let Some(value) = value { let inRange = value >= props.min && value <= props.max; let newValue = (text + "0").parse::().unwrap_or(value); if inRange && newValue > props.max { ctx.focus.focus_next(); + reset_value.set(true); } }; @@ -559,8 +562,9 @@ fn DateSegment( Key::Character(actual_char) => { if actual_char.parse::().is_ok() { let mut text = text_value(); - if text.len() == props.max_length { + if text.len() == props.max_length || reset_value() { text = String::default(); + reset_value.set(false); }; text.push_str(&actual_char); set_value(text);