///|
/// Explicit caller-owned date choices; no system clock or browser date object.
pub struct DatePickerPreset {
label : String
value : @headless.DateSelection
} derive(Eq, Debug)
///|
pub fn date_picker_preset(
label~ : String,
value~ : @headless.CalendarDate,
) -> DatePickerPreset {
{ label, value: @headless.Single(Some(value)), }
}
///|
pub fn date_range_picker_preset(
label~ : String,
start~ : @headless.CalendarDate,
end~ : @headless.CalendarDate,
) -> DatePickerPreset {
let value = if start.ordinal() <= end.ordinal() {
@headless.Range(Some(start), Some(end))
} else {
@headless.Range(Some(end), Some(start))
}
{ label, value, }
}
///|
fn selection_label(
value : @headless.DateSelection,
placeholder : String,
) -> String {
match value {
@headless.Single(Some(date)) => date.iso()
@headless.Multiple(dates) if !dates.is_empty() =>
dates.map(date => date.iso()).join(", ")
@headless.Range(Some(start), Some(end)) => start.iso() + " – " + end.iso()
@headless.Range(Some(start), None) => start.iso() + " – …"
_ => placeholder
}
}
///|
fn selection_start(value : @headless.DateSelection) -> @headless.CalendarDate? {
match value {
@headless.Single(date) | @headless.Range(date, _) => date
@headless.Multiple(dates) => dates.get(0)
}
}
///|
fn selection_enabled(
value : @headless.DateSelection,
disabled_date : (@headless.CalendarDate) -> Bool,
) -> Bool {
match value {
@headless.Single(Some(date)) => !disabled_date(date)
@headless.Multiple(dates) => dates.all(date => !disabled_date(date))
@headless.Range(Some(start), end) =>
!disabled_date(start) &&
end.map(date => !disabled_date(date)).unwrap_or(true)
_ => true
}
}
///|
fn selection_complete(value : @headless.DateSelection) -> Bool {
match value {
@headless.Single(Some(_)) | @headless.Range(Some(_), Some(_)) => true
_ => false
}
}
///|
pub fn date_picker_controlled(
context : UiContext,
id~ : String,
label~ : String,
initial_month~ : @headless.CalendarDate,
selected~ : @minimoon.Val[@headless.DateSelection],
on_change~ : @minimoon.Emit[@headless.DateSelection],
open~ : @minimoon.Val[Bool],
on_open_change~ : @minimoon.Emit[Bool],
disabled_date? : (@headless.CalendarDate) -> Bool = _ => false,
disabled? : Bool = false,
today? : @headless.CalendarDate,
show_outside_days? : Bool = true,
week_start? : Int = 0,
presets? : Array[DatePickerPreset] = [],
format_value? : (@headless.DateSelection) -> String,
close_on_select? : Bool = true,
) -> @minimoon.Val[@minimoon.Node] {
let (month, set_month) = @minimoon.create_variable(initial_month)
let active = @minimoon.Val::map2(open, context.visible, (open, visible) => {
open && visible
})
let choose = form_dispatch(active, (active, value : @headless.DateSelection) => {
if !active || disabled || !selection_enabled(value, disabled_date) {
return @minimoon.none
}
@minimoon.batch([
on_change(value),
selection_start(value)
.map(date => set_month(_ => date))
.unwrap_or(@minimoon.none),
if close_on_select && selection_complete(value) {
on_open_change(false)
} else {
@minimoon.none
},
])
})
let change_month = form_dispatch(active, (
active,
value : @headless.CalendarDate,
) => if active && !disabled { set_month(_ => value) } else { @minimoon.none })
let calendar = calendar_controlled(
id=id + "-calendar",
month~,
on_month_change=change_month,
selected~,
on_change=choose,
disabled_date~,
disabled~,
today?,
show_outside_days~,
week_start~,
)
let preset_nodes = presets.mapi((index, preset) => {
button(
disabled=disabled || !selection_enabled(preset.value, disabled_date),
on_tap=choose(preset.value),
event_key=id + "/preset/" + index.to_string(),
preset.label,
)
})
let content = calendar.map(calendar => {
ui_container("date-picker-content", [
ui_container("date-picker-presets", preset_nodes),
calendar,
])
})
let trigger_label_value = selected.map(value => {
format_value
.map(format => format(value))
.unwrap_or(selection_label(value, label))
})
overlay_controlled(
context,
id~,
title=label,
trigger_label=label,
trigger_label_value~,
content~,
open~,
on_change=on_open_change,
kind=PopoverSurface,
disabled~,
)
}
///|
pub fn date_picker(
context : UiContext,
id~ : String,
label~ : String,
initial_month~ : @headless.CalendarDate,
selected~ : @minimoon.Val[@headless.DateSelection],
on_change~ : @minimoon.Emit[@headless.DateSelection],
disabled_date? : (@headless.CalendarDate) -> Bool = _ => false,
disabled? : Bool = false,
today? : @headless.CalendarDate,
show_outside_days? : Bool = true,
week_start? : Int = 0,
presets? : Array[DatePickerPreset] = [],
format_value? : (@headless.DateSelection) -> String,
initially_open? : Bool = false,
on_open_change? : @minimoon.Emit[Bool],
close_on_select? : Bool = true,
) -> @minimoon.Val[@minimoon.Node] {
let (open, set_open) = @minimoon.create_variable(initially_open && !disabled)
date_picker_controlled(
context,
id~,
label~,
initial_month~,
selected~,
on_change~,
open~,
on_open_change=@minimoon.Emit(value => {
@minimoon.batch([
set_open(_ => value),
on_open_change.map(emit => emit(value)).unwrap_or(@minimoon.none),
])
}),
disabled_date~,
disabled~,
today?,
show_outside_days~,
week_start~,
presets~,
format_value?,
close_on_select~,
)
}
///|
/// Range values use the same typed DateSelection as Calendar; incomplete ranges stay open.
pub fn date_range_picker(
context : UiContext,
id~ : String,
label~ : String,
initial_month~ : @headless.CalendarDate,
selected~ : @minimoon.Val[@headless.DateSelection],
on_change~ : @minimoon.Emit[@headless.DateSelection],
disabled_date? : (@headless.CalendarDate) -> Bool = _ => false,
disabled? : Bool = false,
presets? : Array[DatePickerPreset] = [],
format_value? : (@headless.DateSelection) -> String,
on_open_change? : @minimoon.Emit[Bool],
) -> @minimoon.Val[@minimoon.Node] {
date_picker(
context,
id~,
label~,
initial_month~,
selected~,
on_change~,
disabled_date~,
disabled~,
presets~,
format_value?,
on_open_change?,
)
}