///|
pub fn combobox_provider_controlled(
context : UiContext,
id~ : String,
options~ : @minimoon.Val[Array[ChoiceOption]],
state~ : @minimoon.Val[ComboboxState],
on_change~ : @minimoon.Emit[ComboboxState],
multiple? : Bool = false,
disabled? : Bool = false,
invalid? : Bool = false,
required? : Bool = false,
placeholder? : String = "Choose",
empty_text? : String = "No results",
on_query_change? : @minimoon.Emit[String],
on_values_change? : @minimoon.Emit[Array[String]],
on_open_change? : @minimoon.Emit[Bool],
filter? : (ChoiceOption, String) -> Bool = (option, query) => {
option.label.to_lower().contains(query.to_lower()) ||
option.key.to_lower().contains(query.to_lower())
},
render? : (ComboboxScope) -> @minimoon.Node,
) -> @minimoon.Val[@minimoon.Node] {
let update = combobox_dispatch(
context,
options,
state,
on_change,
multiple~,
disabled~,
on_query_change?,
on_values_change?,
on_open_change?,
)
let bounds = floating_bounds(
context,
id,
state.map(state => state.open && !disabled),
@headless.BottomStart,
)
@minimoon.Val::view4(options, state, bounds, context.visible, (
options,
state,
bounds,
visible,
) => {
validate_choices(options)
let show = update(SetOpen(true))
let scope = ComboboxScope::{
id,
state,
options,
filtered_options: options.filter(option => filter(option, state.query)),
disabled,
invalid,
required,
visible,
bounds,
update,
show,
}
match render {
Some(render) => render(scope)
None => {
let filtered = options.filter(option => filter(option, state.query))
let chips = state.values.map(value => {
combobox_chip(
scope,
value~,
@minimoon.text(select_value(options, value, placeholder=value)),
)
})
ui_container("combobox", id~, [
combobox_trigger(
scope,
label=if state.values.is_empty() {
placeholder
} else {
state.values
.map(key => select_value(options, key, placeholder=key))
.join(", ")
},
),
ui_container("combobox-chips", chips),
if state.open && visible && !disabled {
@minimoon.layer(id=id + "-layer", [
@minimoon.tap_view(
event_key=id + "/dismiss",
on_tap=update(SetOpen(false)),
class="mmui-overlay",
style="position:fixed;inset:0;",
@minimoon.nothing(),
),
ui_container(
"surface",
id=id + "-surface",
style=surface_style(PopoverSurface, bounds),
[
combobox_input(scope),
combobox_clear(scope),
ui_container(
"combobox-list",
semantics=@minimoon.semantics(role=@minimoon.ListboxRole),
filtered.map(option => {
combobox_item(
scope,
value=option.key,
disabled=option.disabled,
@minimoon.text(option.label),
)
}),
),
if filtered.is_empty() {
@minimoon.text(empty_text)
} else {
@minimoon.nothing()
},
],
),
])
} else {
@minimoon.nothing()
},
])
}
}
})
}
///|
pub fn combobox_provider(
context : UiContext,
id~ : String,
options~ : @minimoon.Val[Array[ChoiceOption]],
initial? : ComboboxState = combobox_state(),
multiple? : Bool = false,
disabled? : Bool = false,
invalid? : Bool = false,
required? : Bool = false,
on_query_change? : @minimoon.Emit[String],
on_values_change? : @minimoon.Emit[Array[String]],
on_open_change? : @minimoon.Emit[Bool],
render? : (ComboboxScope) -> @minimoon.Node,
) -> @minimoon.Val[@minimoon.Node] {
let (state, emit) = @minimoon.create_variable(initial)
combobox_provider_controlled(
context,
id~,
options~,
state~,
on_change=emit.map(value => _ => value),
multiple~,
disabled~,
invalid~,
required~,
on_query_change?,
on_values_change?,
on_open_change?,
render?,
)
}
///|
pub fn[C : @minimoon.IsChildren] combobox_content(
scope : ComboboxScope,
children : C,
) -> @minimoon.Node {
if scope.state.open && scope.visible && !scope.disabled {
@minimoon.layer(id=scope.id + "-layer", [
@minimoon.tap_view(
class="mmui-overlay",
style="position:fixed;inset:0;",
event_key=scope.id + "/dismiss",
on_tap=scope.close(),
@minimoon.nothing(),
),
ui_container(
"surface",
id=scope.id + "-surface",
style=surface_style(PopoverSurface, scope.bounds),
children.to_nodes(),
),
])
} else {
@minimoon.nothing()
}
}
///|
pub fn[C : @minimoon.IsChildren] combobox_empty(
children : C,
visible? : Bool = true,
) -> @minimoon.Node {
if visible {
ui_container("combobox-empty", children.to_nodes())
} else {
@minimoon.nothing()
}
}