///|
pub(all) enum Side {
Top
Right
Bottom
Left
} derive(Eq, Debug)
///|
pub(all) enum OverlayKind {
DialogSurface
AlertSurface
SheetSurface(Side)
PopoverSurface
PreviewSurface
TooltipSurface
ContextSurface
} derive(Eq, Debug)
///|
fn surface_style(kind : OverlayKind, bounds : @headless.Bounds?) -> String {
match kind {
SheetSurface(Top) => "position:fixed;top:0;left:0;right:0;max-height:85vh;"
SheetSurface(Bottom) =>
"position:fixed;bottom:0;left:0;right:0;max-height:85vh;"
SheetSurface(Left) =>
"position:fixed;top:0;bottom:0;left:0;width:80vw;max-width:420px;"
SheetSurface(Right) =>
"position:fixed;top:0;bottom:0;right:0;width:80vw;max-width:420px;"
PopoverSurface | PreviewSurface | TooltipSurface | ContextSurface =>
match bounds {
Some(bounds) =>
"position:fixed;left:" +
bounds.left.to_string() +
"px;top:" +
bounds.top.to_string() +
"px;max-width:" +
bounds.width.to_string() +
"px;"
None => "position:fixed;left:8px;top:8px;max-width:calc(100vw - 16px);"
}
_ => "position:fixed;left:5vw;right:5vw;top:20vh;max-height:70vh;"
}
}
///|
fn rect_bounds(rect : @minimoon.NodeRect) -> @headless.Bounds {
{ left: rect.left, top: rect.top, width: rect.width, height: rect.height, }
}
///|
/// One controlled native overlay; content stays reactive while open.
pub fn overlay_controlled(
context : UiContext,
id~ : String,
title~ : String,
trigger_label~ : String,
trigger_label_value? : @minimoon.Val[String],
content~ : @minimoon.Val[@minimoon.Node],
open~ : @minimoon.Val[Bool],
on_change~ : @minimoon.Emit[Bool],
kind? : OverlayKind = DialogSurface,
disabled? : Bool = false,
close_on_overlay? : Bool = true,
placement? : @headless.Placement = @headless.BottomStart,
) -> @minimoon.Val[@minimoon.Node] {
let floating = kind is PopoverSurface ||
kind is PreviewSurface ||
kind is TooltipSurface ||
kind is ContextSurface
let _ = @minimoon.create_state(
(),
update=(_, _message : Unit, _) => @minimoon.no_cmd(()),
subscriptions=(_, _) => {
if floating {
context.page.on_hide(on_change(false))
} else {
@minimoon.Sub::none()
}
},
)
let bounds = if floating {
floating_bounds(context, id, open, placement)
} else {
@minimoon.Val::constant(None)
}
let open_command = if disabled { @minimoon.none } else { on_change(true) }
@minimoon.Val::view5(
open,
content,
bounds,
context.visible,
trigger_label_value.unwrap_or(@minimoon.Val::constant(trigger_label)),
(open, content, bounds, visible, trigger_label) => {
let trigger = if kind is ContextSurface {
@minimoon.touch_view(
id=id + "-trigger",
on_tap=open_command,
on_long_press=@minimoon.Emit(_ => open_command),
event_key=id + "/trigger",
@minimoon.text(trigger_label),
)
} else {
@minimoon.button(
id=id + "-trigger",
disabled~,
on_tap=open_command,
event_key=id + "/trigger",
semantics=@minimoon.semantics(
expanded=open,
controls=id + "-surface",
has_popup="dialog",
),
trigger_label,
)
}
let layer = if open && visible {
let dismiss = if close_on_overlay && !(kind is AlertSurface) {
on_change(false)
} else {
@minimoon.none
}
@minimoon.layer(id=id + "-layer", [
@minimoon.tap_view(
on_tap=dismiss,
event_key=id + "/dismiss",
class="mmui-overlay",
style="position:fixed;left:0;top:0;right:0;bottom:0;",
@minimoon.nothing(),
),
ui_container(
"surface",
id=id + "-surface",
style=surface_style(kind, bounds),
semantics=@minimoon.semantics(
role=if kind is AlertSurface {
@minimoon.AlertDialogRole
} else if kind is TooltipSurface {
@minimoon.TooltipRole
} else {
@minimoon.DialogRole
},
modal=!floating,
labelled_by=id + "-title",
),
[
@minimoon.h1(id=id + "-title", class="mmui-surface-title", title),
content,
@minimoon.button(
on_tap=on_change(false),
event_key=id + "/close",
"Close",
),
],
),
])
} else {
@minimoon.nothing()
}
ui_container("overlay-trigger", [trigger, layer])
},
)
}
///|
pub fn dialog_controlled(
context : UiContext,
id~ : String,
title~ : String,
trigger_label~ : String,
content~ : @minimoon.Val[@minimoon.Node],
open~ : @minimoon.Val[Bool],
on_change~ : @minimoon.Emit[Bool],
close_on_overlay? : Bool = true,
) -> @minimoon.Val[@minimoon.Node] {
overlay_controlled(
context,
id~,
title~,
trigger_label~,
content~,
open~,
on_change~,
close_on_overlay~,
)
}
///|
pub fn dialog(
context : UiContext,
id~ : String,
title~ : String,
trigger_label~ : String,
content~ : @minimoon.Val[@minimoon.Node],
initially_open? : Bool = false,
) -> @minimoon.Val[@minimoon.Node] {
let (open, emit) = @minimoon.create_variable(initially_open)
dialog_controlled(
context,
id~,
title~,
trigger_label~,
content~,
open~,
on_change=emit.map(value => _ => value),
)
}
///|
pub fn sheet(
context : UiContext,
id~ : String,
title~ : String,
trigger_label~ : String,
content~ : @minimoon.Val[@minimoon.Node],
side? : Side = Right,
) -> @minimoon.Val[@minimoon.Node] {
let (open, emit) = @minimoon.create_variable(false)
overlay_controlled(
context,
id~,
title~,
trigger_label~,
content~,
open~,
on_change=emit.map(value => _ => value),
kind=SheetSurface(side),
)
}
///|
pub fn alert_dialog(
context : UiContext,
id~ : String,
title~ : String,
trigger_label~ : String,
content~ : @minimoon.Val[@minimoon.Node],
on_confirm~ : @minimoon.Cmd,
confirm_label? : String = "Confirm",
) -> @minimoon.Val[@minimoon.Node] {
let (open, emit) = @minimoon.create_variable(false)
let confirm = form_dispatch(
@minimoon.Val::map2(open, context.visible, (open, visible) => {
open && visible
}),
(active, _ : Unit) => {
if active {
@minimoon.batch([on_confirm, emit(_ => false)])
} else {
@minimoon.none
}
},
)
let content = content.map(content => {
@minimoon.fragment([
content,
@minimoon.button(
on_tap=confirm(()),
event_key=id + "/confirm",
confirm_label,
),
])
})
overlay_controlled(
context,
id~,
title~,
trigger_label~,
content~,
open~,
on_change=emit.map(value => _ => value),
kind=AlertSurface,
close_on_overlay=false,
)
}
///|
pub fn popover(
context : UiContext,
id~ : String,
title~ : String,
trigger_label~ : String,
content~ : @minimoon.Val[@minimoon.Node],
placement? : @headless.Placement = @headless.BottomStart,
) -> @minimoon.Val[@minimoon.Node] {
let (open, emit) = @minimoon.create_variable(false)
overlay_controlled(
context,
id~,
title~,
trigger_label~,
content~,
open~,
on_change=emit.map(value => _ => value),
kind=PopoverSurface,
placement~,
)
}
///|
pub fn hover_card(
context : UiContext,
id~ : String,
title~ : String,
trigger_label~ : String,
content~ : @minimoon.Val[@minimoon.Node],
) -> @minimoon.Val[@minimoon.Node] {
let (open, emit) = @minimoon.create_variable(false)
overlay_controlled(
context,
id~,
title~,
trigger_label~,
content~,
open~,
on_change=emit.map(value => _ => value),
kind=PreviewSurface,
)
}
///|
pub fn tooltip(
context : UiContext,
id~ : String,
trigger_label~ : String,
text~ : String,
) -> @minimoon.Val[@minimoon.Node] {
let (open, emit) = @minimoon.create_variable(false)
overlay_controlled(
context,
id~,
title=text,
trigger_label~,
content=@minimoon.Val::constant(@minimoon.nothing()),
open~,
on_change=emit.map(value => _ => value),
kind=TooltipSurface,
)
}