///|
pub fn[Input : Eq] navigation_menu_provider_controlled_with_input(
context : UiContext,
id~ : String,
input~ : @minimoon.Val[Input],
value~ : @minimoon.Val[String?],
on_value_change~ : @minimoon.Emit[String?],
render~ : (NavigationMenuScope, Input) -> @minimoon.Node,
viewport? : Bool = true,
aria_label? : String = "Main navigation",
side_offset? : Double = 8.0,
align_offset? : Double = 0.0,
) -> @minimoon.Val[@minimoon.Node] {
let (_, toggle) = @minimoon.create_state_with_input(
input=value,
init=(_, _) => @minimoon.no_cmd(()),
update=(_, current, key : String, _) => {
((), on_value_change(if current == Some(key) { None } else { Some(key) }))
},
)
let (bounds, set_bounds) = @minimoon.create_variable(NavigationBounds::{
value: None,
bounds: None,
})
let _ = @minimoon.create_state(
(),
update=(_, _ : Unit, _) => @minimoon.no_cmd(()),
subscriptions=(_, _) => context.page.on_hide(on_value_change(None)),
)
let watcher = value.switch_by(
(active, _) => {
if active is Some(active) {
let (_, _) = @minimoon.create_state_with_init(
init=emit => {
(
(),
@minimoon.measure_nodes(
[
navigation_part_id(id, active, "trigger"),
if viewport {
id + "-viewport"
} else {
navigation_part_id(id, active, "content")
},
"mmui-viewport",
],
emit,
),
)
},
update=(
_,
result : Result[Array[@minimoon.NodeRect?], @minimoon.HostError],
_,
) => {
let placement = match result {
Ok([Some(anchor), Some(surface), Some(window)]) => {
let placed = @headless.place(
rect_bounds(anchor),
rect_bounds(surface),
rect_bounds(window),
@headless.BottomStart,
gap=side_offset,
)
NavigationBounds::{
value: Some(active),
bounds: Some({
..placed,
left: (placed.left + align_offset)
.max(window.left)
.min((window.right - placed.width).max(window.left)),
}),
}
}
_ => NavigationBounds::{ value: Some(active), bounds: None, }
}
((), set_bounds(_ => placement))
},
)
}
@minimoon.Val::constant(@minimoon.nothing())
},
by=active => active.map(value => "value:" + value).unwrap_or("closed"),
)
let rendered = @minimoon.Val::view4(value, input, bounds, context.visible, (
active,
input,
placement,
visible,
) => {
let scope = NavigationMenuScope::{
id,
value: if visible {
active
} else {
None
},
viewport,
bounds: if placement.value == active {
placement.bounds
} else {
None
},
change: on_value_change,
toggle,
contents: Ref(None),
chrome: Ref(None),
keys: Set([]),
}
let content = render(scope, input)
let shared = if viewport && visible && scope.contents.val is Some(content) {
navigation_surface(
scope,
id + "-viewport",
@minimoon.fragment([
content,
scope.chrome.val.unwrap_or(@minimoon.nothing()),
]),
)
} else {
@minimoon.nothing()
}
ui_container(
"navigation-menu-root",
id~,
semantics=@minimoon.semantics(
role=@minimoon.NavigationRole,
label=aria_label,
),
[content, shared],
)
})
@minimoon.Val::view2(rendered, watcher, (rendered, watcher) => {
@minimoon.fragment([rendered, watcher])
})
}
///|
pub fn navigation_menu_provider_controlled(
context : UiContext,
id~ : String,
value~ : @minimoon.Val[String?],
on_value_change~ : @minimoon.Emit[String?],
render~ : (NavigationMenuScope) -> @minimoon.Node,
viewport? : Bool = true,
aria_label? : String = "Main navigation",
side_offset? : Double = 8.0,
align_offset? : Double = 0.0,
) -> @minimoon.Val[@minimoon.Node] {
navigation_menu_provider_controlled_with_input(
context,
id~,
input=@minimoon.Val::constant(()),
value~,
on_value_change~,
render=(scope, _) => render(scope),
viewport~,
aria_label~,
side_offset~,
align_offset~,
)
}
///|
pub fn[Input : Eq] navigation_menu_provider_with_input(
context : UiContext,
id~ : String,
input~ : @minimoon.Val[Input],
render~ : (NavigationMenuScope, Input) -> @minimoon.Node,
default_value? : String,
viewport? : Bool = true,
on_value_change? : @minimoon.Emit[String?],
aria_label? : String = "Main navigation",
side_offset? : Double = 8.0,
align_offset? : Double = 0.0,
) -> @minimoon.Val[@minimoon.Node] {
let (value, set_value) = @minimoon.create_variable(default_value)
navigation_menu_provider_controlled_with_input(
context,
id~,
input~,
value~,
on_value_change=@minimoon.Emit(value => {
@minimoon.batch([
set_value(_ => value),
on_value_change.map(emit => emit(value)).unwrap_or(@minimoon.none),
])
}),
render~,
viewport~,
aria_label~,
side_offset~,
align_offset~,
)
}
///|
pub fn navigation_menu_root(
context : UiContext,
id~ : String,
render~ : (NavigationMenuScope) -> @minimoon.Node,
default_value? : String,
viewport? : Bool = true,
on_value_change? : @minimoon.Emit[String?],
aria_label? : String = "Main navigation",
side_offset? : Double = 8.0,
align_offset? : Double = 0.0,
) -> @minimoon.Val[@minimoon.Node] {
navigation_menu_provider_with_input(
context,
id~,
input=@minimoon.Val::constant(()),
render=(scope, _) => render(scope),
default_value?,
viewport~,
on_value_change?,
aria_label~,
side_offset~,
align_offset~,
)
}