///|
/// A caller-defined quick choice rendered inside [`date_picker`].
struct DatePickerPreset {
label : String
value : CalendarDate
} derive(Debug, Eq)
///|
pub fn date_picker_preset(
label~ : String,
value~ : CalendarDate,
) -> DatePickerPreset {
{ label, value }
}
///|
/// An opaque range value. `end()` is `None` while the user is choosing the
/// second boundary.
struct DateRange {
start : CalendarDate
end : CalendarDate?
} derive(Debug, Eq)
///|
/// Construct a normalized date range. If `end` precedes `start`, the two
/// boundaries are exchanged.
pub fn date_range(start~ : CalendarDate, end? : CalendarDate) -> DateRange {
if end is Some(end) && calendar_compare_dates(end, start) < 0 {
{ start: end, end: Some(start) }
} else {
{ start, end }
}
}
///|
pub fn DateRange::start(self : DateRange) -> CalendarDate {
self.start
}
///|
pub fn DateRange::end(self : DateRange) -> CalendarDate? {
self.end
}
///|
priv struct DatePickerModel {
open : Bool
visible : CalendarDate
value : CalendarDate?
focused : CalendarDate
} derive(Eq)
///|
priv struct DateRangePickerModel {
open : Bool
visible : CalendarDate
value : DateRange?
focused : CalendarDate
} derive(Eq)
///|
#cfg(target="js")
priv enum DatePickerMsg {
DatePickerToggle
DatePickerNativeChanged(Bool)
DatePickerNavigate(Int)
DatePickerChoose(CalendarDate)
DatePickerSetFocused(CalendarDate)
DatePickerMoveFocus(CalendarDate)
}
///|
#cfg(target="js")
priv enum DateRangePickerMsg {
DateRangePickerToggle
DateRangePickerNativeChanged(Bool)
DateRangePickerNavigate(Int)
DateRangePickerChoose(CalendarDate)
DateRangePickerSetFocused(CalendarDate)
DateRangePickerMoveFocus(CalendarDate)
}
///|
const DatePickerRootStyle : String = "position:relative;display:inline-flex;width:fit-content;max-width:100%;min-width:0"
///|
const DatePickerTriggerStyle : String = "width:17.5rem;max-width:100%;justify-content:flex-start;gap:0.5rem;font-weight:400;text-align:left"
///|
const DatePickerTriggerLabelStyle : String = "min-width:0;flex:1;overflow:hidden;text-overflow:ellipsis;white-space:nowrap"
///|
const DatePickerPlaceholderStyle : String = "color:var(--rui-muted-foreground,oklch(0.556 0 0))"
///|
const DatePickerContentStyle : String = "width:15.5rem;min-width:0;max-width:calc(100vw - 1rem);gap:0;overflow:hidden;padding:0;box-shadow:0 4px 6px -1px rgb(0 0 0 / 0.1),0 2px 4px -2px rgb(0 0 0 / 0.1),0 0 0 1px color-mix(in oklab,var(--rui-foreground,oklch(0.145 0 0)) 10%,transparent)"
///|
const DateRangePickerContentStyle : String = "width:var(--rui-date-range-picker-width,32rem);min-width:0;max-width:calc(100vw - 1rem);max-height:var(--rui-date-range-picker-max-height,none);position-try-order:most-height;gap:0;overflow-x:hidden;overflow-y:var(--rui-date-range-picker-overflow-y,hidden);padding:0;box-shadow:0 4px 6px -1px rgb(0 0 0 / 0.1),0 2px 4px -2px rgb(0 0 0 / 0.1),0 0 0 1px color-mix(in oklab,var(--rui-foreground,oklch(0.145 0 0)) 10%,transparent)"
///|
const DatePickerContentInnerStyle : String = "display:flex;min-width:0;flex-direction:column"
///|
const DateRangePickerContentInnerStyle : String = "display:flex;width:100%;min-width:0;max-width:100%;flex-wrap:wrap;align-items:flex-start;justify-content:center;gap:1rem"
///|
const DatePickerPresetsStyle : String = "display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:0.25rem;border-bottom:1px solid var(--rui-border,oklch(0.922 0 0));padding:0.5rem"
///|
const DatePickerPresetStyle : String = "width:100%;justify-content:flex-start"
///|
const DatePickerCalendarStyle : String = "width:15.5rem;min-width:0;max-width:100%;border-radius:0"
///|
const DateRangePickerCalendarStyle : String = "width:15.5rem;min-width:0;max-width:100%;flex:0 1 15.5rem;border-radius:0"
///|
fn date_picker_part_styles(
base : String,
overrides : Array[String],
) -> Array[String] {
let styles = [base]
for custom_style in overrides {
styles.push(custom_style)
}
styles
}
///|
fn date_picker_initial_model(
default_open : Bool,
disabled : Bool,
default_month : CalendarDate?,
default_value : CalendarDate?,
today : CalendarDate?,
) -> DatePickerModel {
let calendar = calendar_initial_model(default_month, default_value, today)
{
open: default_open && !disabled,
visible: calendar.visible,
value: default_value,
focused: calendar.focused,
}
}
///|
fn date_range_picker_initial_model(
default_open : Bool,
disabled : Bool,
default_month : CalendarDate?,
default_value : DateRange?,
today : CalendarDate?,
) -> DateRangePickerModel {
let selected = default_value.map(value => value.start)
let calendar = calendar_initial_model(default_month, selected, today)
{
open: default_open && !disabled,
visible: calendar.visible,
value: default_value,
focused: calendar.focused,
}
}
///|
fn date_picker_label(
value : CalendarDate?,
placeholder : String,
format_value : ((CalendarDate) -> String)?,
) -> String {
if value is Some(value) {
if format_value is Some(format) {
format(value)
} else {
calendar_date_label(value)
}
} else {
placeholder
}
}
///|
fn date_range_picker_default_label(value : DateRange) -> String {
let start = calendar_date_label(value.start)
if value.end is Some(end) {
"\{start} – \{calendar_date_label(end)}"
} else {
start
}
}
///|
fn date_range_picker_label(
value : DateRange?,
placeholder : String,
format_value : ((DateRange) -> String)?,
) -> String {
if value is Some(value) {
if format_value is Some(format) {
format(value)
} else {
date_range_picker_default_label(value)
}
} else {
placeholder
}
}
///|
fn date_picker_trigger(
id : String,
open : Bool,
has_value : Bool,
disabled : Bool,
invalid : Bool,
aria_label : String?,
label : String,
on_toggle : @cmd.Cmd?,
attrs : @html.Attrs?,
style : Array[String],
) -> @html.Html {
let trigger_id = "\{id}-trigger"
let element_attrs = ui_attrs(attrs)
.data_set("slot", "date-picker-trigger")
.data_set("variant", "outline")
.data_set("size", "default")
.data_set("state", if open { "open" } else { "closed" })
.data_set("placeholder", ui_bool(!has_value))
.aria_haspopup("dialog")
.aria_expanded(ui_bool(open))
.aria_controls(id)
.aria_disabled(ui_bool(disabled))
if aria_label is Some(aria_label) {
ignore(element_attrs.aria_label(aria_label))
}
if open {
ignore(element_attrs.data_set("open", ""))
}
if disabled {
ignore(element_attrs.data_set("disabled", ""))
} else if on_toggle is Some(command) {
ignore(element_attrs.on_click(_ => command))
}
if invalid {
ignore(element_attrs.data_set("invalid", "").aria_invalid("true"))
}
@html.button(
style=ui_styles(
[
UiBoxSizing,
UiFontSans,
UiTextRendering,
UiTransition,
ButtonBaseStyle,
button_variant_style(Outline),
button_size_style(Default),
DatePickerTriggerStyle,
if invalid {
InputInvalidStyle
} else {
""
},
ui_disabled_style(disabled),
popup_anchor_style(trigger_id),
],
style,
),
id=trigger_id,
type_="button",
disabled~,
attrs=element_attrs,
[
ui_calendar_icon(),
@html.span(
style=ui_styles(
[
UiBoxSizing,
DatePickerTriggerLabelStyle,
if has_value {
""
} else {
DatePickerPlaceholderStyle
},
],
[],
),
label,
),
],
)
}
///|
fn date_picker_presets(
presets : Array[DatePickerPreset],
disabled : Bool,
choose : @cmd.Emit[CalendarDate]?,
style : Array[String],
) -> @html.Html {
if presets.length() == 0 {
@html.nothing
} else {
let items = presets.map(preset => {
let command = choose.map(choose => choose(preset.value))
button(
variant=Ghost,
size=Sm,
disabled~,
on_click?=command,
attrs=@html.Attrs::build().data_set(
"preset-value",
preset.value.to_iso_string(),
),
style=date_picker_part_styles(DatePickerPresetStyle, style),
slot="date-picker-preset",
preset.label,
)
})
@html.div(
style=[UiBoxSizing, DatePickerPresetsStyle],
attrs=@html.Attrs::build()
.data_set("slot", "date-picker-presets")
.aria_label("Date presets"),
items,
)
}
}
///|
fn date_picker_root_attrs(
attrs : @html.Attrs?,
slot : String,
open : Bool,
disabled : Bool,
invalid : Bool,
) -> @html.Attrs {
let root_attrs = ui_attrs(attrs)
.data_set("slot", slot)
.data_set("state", if open { "open" } else { "closed" })
.data_set("disabled", ui_bool(disabled))
.data_set("invalid", ui_bool(invalid))
if open {
ignore(root_attrs.data_set("open", ""))
}
root_attrs
}
///|
fn render_date_picker(
id : String,
model : DatePickerModel,
today : CalendarDate?,
show_outside_days : Bool,
placeholder : String,
disabled : Bool,
invalid : Bool,
aria_label : String?,
side : PopupSide,
align : PopupAlign,
side_offset : Int,
align_offset : Int,
presets : Array[DatePickerPreset],
format_value : ((CalendarDate) -> String)?,
class : String?,
title : String?,
attrs : @html.Attrs?,
style : Array[String],
trigger_attrs : @html.Attrs?,
trigger_style : Array[String],
content_attrs : @html.Attrs?,
content_style : Array[String],
calendar_attrs : @html.Attrs?,
calendar_style : Array[String],
day_attrs : @html.Attrs?,
day_style : Array[String],
preset_style : Array[String],
on_toggle : @cmd.Cmd?,
on_navigate : @cmd.Emit[Int]?,
on_choose : @cmd.Emit[CalendarDate]?,
on_focus_change : @cmd.Emit[CalendarDate]?,
on_focus_move : @cmd.Emit[CalendarDate]?,
) -> @html.Html {
let label = date_picker_label(model.value, placeholder, format_value)
let root_attrs = date_picker_root_attrs(
attrs,
"date-picker",
model.open,
disabled,
invalid,
)
if model.value is Some(value) {
ignore(root_attrs.data_set("value", value.to_iso_string()))
}
let previous = on_navigate.map(navigate => navigate(-1))
let next = on_navigate.map(navigate => navigate(1))
let calendar_id = "\{id}-calendar"
let selected = model.value
@html.span(
style=ui_styles([UiBoxSizing, DatePickerRootStyle], style),
id="\{id}-root",
class?,
title?,
attrs=root_attrs,
[
date_picker_trigger(
id,
model.open,
model.value is Some(_),
disabled,
invalid,
aria_label,
label,
on_toggle,
trigger_attrs,
trigger_style,
),
popover_content(
id~,
trigger_id="\{id}-trigger",
open=model.open,
initial_focus=true,
restore_focus=true,
final_focus_id="\{id}-trigger",
side~,
align~,
side_offset~,
align_offset~,
attrs?=content_attrs,
style=date_picker_part_styles(DatePickerContentStyle, content_style),
@html.div(
style=[UiBoxSizing, DatePickerContentInnerStyle],
attrs=@html.Attrs::build().data_set("slot", "date-picker-content"),
[
date_picker_presets(presets, disabled, on_choose, preset_style),
render_calendar(
month=model.visible,
selected?,
today?,
focused=model.focused,
show_outside_days~,
disabled~,
aria_label="Date picker calendar",
on_previous?=previous,
on_next?=next,
on_select?=on_choose,
on_focus_change?,
on_focus_move?,
id=calendar_id,
attrs?=calendar_attrs,
day_attrs?,
style=date_picker_part_styles(
DatePickerCalendarStyle,
calendar_style,
),
day_style~,
),
],
),
),
],
)
}
///|
fn render_date_range_picker(
id : String,
model : DateRangePickerModel,
today : CalendarDate?,
show_outside_days : Bool,
placeholder : String,
disabled : Bool,
invalid : Bool,
aria_label : String?,
side : PopupSide,
align : PopupAlign,
side_offset : Int,
align_offset : Int,
format_value : ((DateRange) -> String)?,
class : String?,
title : String?,
attrs : @html.Attrs?,
style : Array[String],
trigger_attrs : @html.Attrs?,
trigger_style : Array[String],
content_attrs : @html.Attrs?,
content_style : Array[String],
calendar_attrs : @html.Attrs?,
calendar_style : Array[String],
day_attrs : @html.Attrs?,
day_style : Array[String],
on_toggle : @cmd.Cmd?,
on_navigate : @cmd.Emit[Int]?,
on_choose : @cmd.Emit[CalendarDate]?,
on_focus_change : @cmd.Emit[CalendarDate]?,
on_focus_move : @cmd.Emit[CalendarDate]?,
) -> @html.Html {
let label = date_range_picker_label(model.value, placeholder, format_value)
let root_attrs = date_picker_root_attrs(
attrs,
"date-range-picker",
model.open,
disabled,
invalid,
)
let range_start = model.value.map(value => value.start)
let range_end = if model.value is Some(value) { value.end } else { None }
if range_start is Some(start) {
ignore(root_attrs.data_set("range-start", start.to_iso_string()))
}
if range_end is Some(end) {
ignore(root_attrs.data_set("range-end", end.to_iso_string()))
}
let previous = on_navigate.map(navigate => navigate(-1))
let next = on_navigate.map(navigate => navigate(1))
let following_month = calendar_shift_month(model.visible, 1)
let range_calendar_style = date_picker_part_styles(
DateRangePickerCalendarStyle,
calendar_style,
)
let range_content_attrs = ui_attrs(content_attrs).data_set(
"date-range-picker-popup", "",
)
@html.span(
style=ui_styles([UiBoxSizing, DatePickerRootStyle], style),
id="\{id}-root",
class?,
title?,
attrs=root_attrs,
[
date_picker_trigger(
id,
model.open,
model.value is Some(_),
disabled,
invalid,
aria_label,
label,
on_toggle,
trigger_attrs,
trigger_style,
),
popover_content(
id~,
trigger_id="\{id}-trigger",
open=model.open,
initial_focus=true,
restore_focus=true,
final_focus_id="\{id}-trigger",
side~,
align~,
side_offset~,
align_offset~,
attrs=range_content_attrs,
style=date_picker_part_styles(
DateRangePickerContentStyle,
content_style,
),
@html.div(
style=[UiBoxSizing, DateRangePickerContentInnerStyle],
attrs=@html.Attrs::build().data_set(
"slot", "date-range-picker-content",
),
[
render_calendar(
month=model.visible,
selection_mode=CalendarRangeSelection,
range_start?,
range_end?,
today?,
focused=model.focused,
show_outside_days~,
hide_outside_after=true,
disabled~,
aria_label="Date range picker calendar",
on_previous?=previous,
on_select?=on_choose,
on_focus_change?,
on_focus_move?,
show_next=false,
id="\{id}-calendar",
attrs?=calendar_attrs,
day_attrs?,
style=range_calendar_style,
nav_style=["justify-content:flex-start"],
day_style~,
),
render_calendar(
month=following_month,
selection_mode=CalendarRangeSelection,
range_start?,
range_end?,
today?,
focused=model.focused,
show_outside_days~,
hide_outside_before=true,
disabled~,
aria_label="Date range picker following calendar",
on_next?=next,
on_select?=on_choose,
on_focus_change?,
on_focus_move?,
show_previous=false,
id="\{id}-calendar-next",
attrs?=calendar_attrs,
day_attrs?,
style=range_calendar_style,
nav_style=["justify-content:flex-end"],
day_style~,
),
],
),
),
],
)
}
///|
#cfg(target="js")
fn[T] date_picker_notify(emit : @cmd.Emit[T]?, value : T) -> @cmd.Cmd {
if emit is Some(emit) {
emit(value)
} else {
@cmd.none
}
}
///|
#cfg(target="js")
fn date_picker_update(
id : String,
disabled : Bool,
today : CalendarDate?,
on_value_change : @cmd.Emit[CalendarDate]?,
on_open_change : @cmd.Emit[Bool]?,
message : DatePickerMsg,
current : DatePickerModel,
) -> (DatePickerModel, @cmd.Cmd) {
match message {
DatePickerToggle =>
if disabled {
(current, @cmd.none)
} else {
let open = !current.open
(
{ ..current, open, },
@cmd.batch([
floating_set_open_cmd(id, open),
date_picker_notify(on_open_change, open),
]),
)
}
DatePickerNativeChanged(open) =>
if open == current.open {
(current, @cmd.none)
} else {
({ ..current, open, }, date_picker_notify(on_open_change, open))
}
DatePickerNavigate(delta) => {
let visible = calendar_shift_month(current.visible, delta)
let focused = calendar_focus_target(visible, current.value, today)
({ ..current, visible, focused }, @cmd.none)
}
DatePickerChoose(value) => {
let visible = calendar_first_of_month(value)
(
{ open: false, visible, value: Some(value), focused: value },
@cmd.batch([
date_picker_notify(on_value_change, value),
floating_set_open_cmd(id, false),
if current.open {
date_picker_notify(on_open_change, false)
} else {
@cmd.none
},
]),
)
}
DatePickerSetFocused(value) => ({ ..current, focused: value }, @cmd.none)
DatePickerMoveFocus(value) => {
let visible = calendar_first_of_month(value)
(
{ ..current, visible, focused: value },
calendar_focus_day_cmd(Some("\{id}-calendar"), value),
)
}
}
}
///|
fn date_range_picker_next_value(
current : DateRange?,
chosen : CalendarDate,
) -> (DateRange, Bool) {
let complete = current is Some(range) && range.end is None
(calendar_range_next_value(current, chosen), complete)
}
///|
fn date_range_picker_visible_for_date(
visible : CalendarDate,
date : CalendarDate,
) -> CalendarDate {
let visible = calendar_first_of_month(visible)
let following = calendar_shift_month(visible, 1)
if calendar_same_month(date, visible) || calendar_same_month(date, following) {
visible
} else {
calendar_first_of_month(date)
}
}
///|
fn date_range_picker_focus_calendar_id(
id : String,
visible : CalendarDate,
date : CalendarDate,
) -> String {
if calendar_same_month(date, calendar_shift_month(visible, 1)) {
"\{id}-calendar-next"
} else {
"\{id}-calendar"
}
}
///|
#cfg(target="js")
fn date_range_picker_update(
id : String,
disabled : Bool,
today : CalendarDate?,
on_value_change : @cmd.Emit[DateRange]?,
on_open_change : @cmd.Emit[Bool]?,
message : DateRangePickerMsg,
current : DateRangePickerModel,
) -> (DateRangePickerModel, @cmd.Cmd) {
match message {
DateRangePickerToggle =>
if disabled {
(current, @cmd.none)
} else {
let open = !current.open
(
{ ..current, open, },
@cmd.batch([
floating_set_open_cmd(id, open),
date_picker_notify(on_open_change, open),
]),
)
}
DateRangePickerNativeChanged(open) =>
if open == current.open {
(current, @cmd.none)
} else {
({ ..current, open, }, date_picker_notify(on_open_change, open))
}
DateRangePickerNavigate(delta) => {
let visible = calendar_shift_month(current.visible, delta)
let selected = current.value.map(value => value.start)
let focused = calendar_focus_target(visible, selected, today)
({ ..current, visible, focused }, @cmd.none)
}
DateRangePickerChoose(chosen) => {
let (value, complete) = date_range_picker_next_value(
current.value,
chosen,
)
let visible = date_range_picker_visible_for_date(current.visible, chosen)
let open = !complete
(
{ open, visible, value: Some(value), focused: chosen },
@cmd.batch([
date_picker_notify(on_value_change, value),
if complete {
floating_set_open_cmd(id, false)
} else {
@cmd.none
},
if complete && current.open {
date_picker_notify(on_open_change, false)
} else {
@cmd.none
},
]),
)
}
DateRangePickerSetFocused(value) =>
({ ..current, focused: value }, @cmd.none)
DateRangePickerMoveFocus(value) => {
let visible = date_range_picker_visible_for_date(current.visible, value)
let focus_calendar_id = date_range_picker_focus_calendar_id(
id, visible, value,
)
(
{ ..current, visible, focused: value },
calendar_focus_day_cmd(Some(focus_calendar_id), value),
)
}
}
}
///|
/// Stateful single-date picker composed from the package's native Calendar
/// and CSS-anchored Popover primitives. Selecting a date updates the trigger,
/// calls `on_value_change`, and closes the popup.
#cfg(target="js")
pub fn date_picker(
id~ : String,
default_value? : CalendarDate,
default_month? : CalendarDate,
today? : CalendarDate,
default_open? : Bool = false,
show_outside_days? : Bool = true,
placeholder? : String = "Pick a date",
disabled? : Bool = false,
invalid? : Bool = false,
aria_label? : String,
side? : PopupSide = Bottom,
align? : PopupAlign = Start,
side_offset? : Int = 4,
align_offset? : Int = 0,
presets? : Array[DatePickerPreset] = [],
format_value? : (CalendarDate) -> String,
on_value_change? : @cmd.Emit[CalendarDate],
on_open_change? : @cmd.Emit[Bool],
class? : String,
title? : String,
attrs? : @html.Attrs,
style? : Array[String] = [],
trigger_attrs? : @html.Attrs,
trigger_style? : Array[String] = [],
content_attrs? : @html.Attrs,
content_style? : Array[String] = [],
calendar_attrs? : @html.Attrs,
calendar_style? : Array[String] = [],
day_attrs? : @html.Attrs,
day_style? : Array[String] = [],
preset_style? : Array[String] = [],
) -> @rabbita.Val[@html.Html] {
let initial = date_picker_initial_model(
default_open, disabled, default_month, default_value, today,
)
let (model, emit) = @rabbita.create_state_with_init(
init=emit => {
(
initial,
@cmd.batch([
floating_bind_sync_cmd(
id,
emit.map(open => DatePickerNativeChanged(open)),
),
if initial.open {
floating_set_open_cmd(id, true)
} else {
@cmd.none
},
]),
)
},
update=(_, message, current) => {
date_picker_update(
id, disabled, today, on_value_change, on_open_change, message, current,
)
},
)
let navigate = emit.map(delta => DatePickerNavigate(delta))
let choose = emit.map(value => DatePickerChoose(value))
let focus = emit.map(value => DatePickerSetFocused(value))
let move_focus = emit.map(value => DatePickerMoveFocus(value))
model.view(model => {
render_date_picker(
id,
model,
today,
show_outside_days,
placeholder,
disabled,
invalid,
aria_label,
side,
align,
side_offset,
align_offset,
presets,
format_value,
class,
title,
attrs,
style,
trigger_attrs,
trigger_style,
content_attrs,
content_style,
calendar_attrs,
calendar_style,
day_attrs,
day_style,
preset_style,
Some(emit(DatePickerToggle)),
Some(navigate),
Some(choose),
Some(focus),
Some(move_focus),
)
})
}
///|
/// Native/SSR fallback for [`date_picker`].
#cfg(not(target="js"))
pub fn date_picker(
id~ : String,
default_value? : CalendarDate,
default_month? : CalendarDate,
today? : CalendarDate,
default_open? : Bool = false,
show_outside_days? : Bool = true,
placeholder? : String = "Pick a date",
disabled? : Bool = false,
invalid? : Bool = false,
aria_label? : String,
side? : PopupSide = Bottom,
align? : PopupAlign = Start,
side_offset? : Int = 4,
align_offset? : Int = 0,
presets? : Array[DatePickerPreset] = [],
format_value? : (CalendarDate) -> String,
on_value_change? : @cmd.Emit[CalendarDate],
on_open_change? : @cmd.Emit[Bool],
class? : String,
title? : String,
attrs? : @html.Attrs,
style? : Array[String] = [],
trigger_attrs? : @html.Attrs,
trigger_style? : Array[String] = [],
content_attrs? : @html.Attrs,
content_style? : Array[String] = [],
calendar_attrs? : @html.Attrs,
calendar_style? : Array[String] = [],
day_attrs? : @html.Attrs,
day_style? : Array[String] = [],
preset_style? : Array[String] = [],
) -> @rabbita.Val[@html.Html] {
ignore((on_value_change, on_open_change))
let initial = date_picker_initial_model(
default_open, disabled, default_month, default_value, today,
)
let choose : @cmd.Emit[CalendarDate] = Emit(_ => @cmd.none)
@rabbita.Val::constant(
render_date_picker(
id,
initial,
today,
show_outside_days,
placeholder,
disabled,
invalid,
aria_label,
side,
align,
side_offset,
align_offset,
presets,
format_value,
class,
title,
attrs,
style,
trigger_attrs,
trigger_style,
content_attrs,
content_style,
calendar_attrs,
calendar_style,
day_attrs,
day_style,
preset_style,
None,
None,
Some(choose),
None,
None,
),
)
}
///|
/// Stateful date-range picker. The first click starts a range and emits a
/// `DateRange` whose `end()` is `None`; the second click normalizes its
/// boundaries, emits the completed range, and closes the popup. Clicking after
/// a completed range starts a new selection.
#cfg(target="js")
pub fn date_range_picker(
id~ : String,
default_value? : DateRange,
default_month? : CalendarDate,
today? : CalendarDate,
default_open? : Bool = false,
show_outside_days? : Bool = true,
placeholder? : String = "Pick a date range",
disabled? : Bool = false,
invalid? : Bool = false,
aria_label? : String,
side? : PopupSide = Bottom,
align? : PopupAlign = Start,
side_offset? : Int = 4,
align_offset? : Int = 0,
format_value? : (DateRange) -> String,
on_value_change? : @cmd.Emit[DateRange],
on_open_change? : @cmd.Emit[Bool],
class? : String,
title? : String,
attrs? : @html.Attrs,
style? : Array[String] = [],
trigger_attrs? : @html.Attrs,
trigger_style? : Array[String] = [],
content_attrs? : @html.Attrs,
content_style? : Array[String] = [],
calendar_attrs? : @html.Attrs,
calendar_style? : Array[String] = [],
day_attrs? : @html.Attrs,
day_style? : Array[String] = [],
) -> @rabbita.Val[@html.Html] {
let initial = date_range_picker_initial_model(
default_open, disabled, default_month, default_value, today,
)
let (model, emit) = @rabbita.create_state_with_init(
init=emit => {
(
initial,
@cmd.batch([
floating_bind_sync_cmd(
id,
emit.map(open => DateRangePickerNativeChanged(open)),
),
if initial.open {
floating_set_open_cmd(id, true)
} else {
@cmd.none
},
]),
)
},
update=(_, message, current) => {
date_range_picker_update(
id, disabled, today, on_value_change, on_open_change, message, current,
)
},
)
let navigate = emit.map(delta => DateRangePickerNavigate(delta))
let choose = emit.map(value => DateRangePickerChoose(value))
let focus = emit.map(value => DateRangePickerSetFocused(value))
let move_focus = emit.map(value => DateRangePickerMoveFocus(value))
model.view(model => {
render_date_range_picker(
id,
model,
today,
show_outside_days,
placeholder,
disabled,
invalid,
aria_label,
side,
align,
side_offset,
align_offset,
format_value,
class,
title,
attrs,
style,
trigger_attrs,
trigger_style,
content_attrs,
content_style,
calendar_attrs,
calendar_style,
day_attrs,
day_style,
Some(emit(DateRangePickerToggle)),
Some(navigate),
Some(choose),
Some(focus),
Some(move_focus),
)
})
}
///|
/// Native/SSR fallback for [`date_range_picker`].
#cfg(not(target="js"))
pub fn date_range_picker(
id~ : String,
default_value? : DateRange,
default_month? : CalendarDate,
today? : CalendarDate,
default_open? : Bool = false,
show_outside_days? : Bool = true,
placeholder? : String = "Pick a date range",
disabled? : Bool = false,
invalid? : Bool = false,
aria_label? : String,
side? : PopupSide = Bottom,
align? : PopupAlign = Start,
side_offset? : Int = 4,
align_offset? : Int = 0,
format_value? : (DateRange) -> String,
on_value_change? : @cmd.Emit[DateRange],
on_open_change? : @cmd.Emit[Bool],
class? : String,
title? : String,
attrs? : @html.Attrs,
style? : Array[String] = [],
trigger_attrs? : @html.Attrs,
trigger_style? : Array[String] = [],
content_attrs? : @html.Attrs,
content_style? : Array[String] = [],
calendar_attrs? : @html.Attrs,
calendar_style? : Array[String] = [],
day_attrs? : @html.Attrs,
day_style? : Array[String] = [],
) -> @rabbita.Val[@html.Html] {
ignore((on_value_change, on_open_change))
let initial = date_range_picker_initial_model(
default_open, disabled, default_month, default_value, today,
)
let choose : @cmd.Emit[CalendarDate] = Emit(_ => @cmd.none)
@rabbita.Val::constant(
render_date_range_picker(
id,
initial,
today,
show_outside_days,
placeholder,
disabled,
invalid,
aria_label,
side,
align,
side_offset,
align_offset,
format_value,
class,
title,
attrs,
style,
trigger_attrs,
trigger_style,
content_attrs,
content_style,
calendar_attrs,
calendar_style,
day_attrs,
day_style,
None,
None,
Some(choose),
None,
None,
),
)
}