///|
struct ContextMenuScope {
menu : MenuScope
point_x : Int
point_y : Int
}
///|
struct ContextMenuRadioGroupScope {
menu : MenuScope
group : String
}
///|
struct ContextMenuSubScope {
menu : MenuScope
key : String
}
///|
#cfg(target="js")
priv struct ContextMenuModel {
menu : MenuCoreModel
x : Int
y : Int
point_x : Int
point_y : Int
} derive(Eq)
///|
#cfg(target="js")
priv enum ContextMenuMsg {
ContextOpenAt(Int, Int, Int, Int)
ContextSetOpen(Bool)
ContextNativeOpen(Bool)
ContextSetActive(Int)
ContextToggleChecked(String)
ContextSetRadio(String, String)
ContextSetSubmenu(MenuSubmenuChange)
}
///|
fn context_menu_point_id(trigger_id : String) -> String {
trigger_id + "-point"
}
///|
const ContextMenuPointStyle : String = "position:absolute;width:0;height:0;pointer-events:none"
///|
#cfg(target="js")
priv struct ContextMenuTriggerBinding {
element : @dom.Element
open_at : Ref[(Int, Int, Int, Int) -> Unit]
}
///|
#cfg(target="js")
let context_menu_trigger_bindings : Map[String, ContextMenuTriggerBinding] = Map([],
)
///|
#cfg(target="js")
fn context_menu_open_from_point(
binding : ContextMenuTriggerBinding,
client_x : Double,
client_y : Double,
) -> Unit {
let trigger = binding.element
let rect = trigger.get_bounding_client_rect()
let width = if trigger.get_client_width() > 0.0 {
trigger.get_client_width()
} else {
rect.get_width()
}
let height = if trigger.get_client_height() > 0.0 {
trigger.get_client_height()
} else {
rect.get_height()
}
let point_x = (client_x - rect.get_left() - trigger.get_client_left())
.max(0.0)
.min(width)
let point_y = (client_y - rect.get_top() - trigger.get_client_top())
.max(0.0)
.min(height)
(binding.open_at.val)(
client_x.round().to_int(),
client_y.round().to_int(),
point_x.round().to_int(),
point_y.round().to_int(),
)
}
///|
#cfg(target="js")
fn bind_context_menu_trigger(
id : String,
open_at : (Int, Int, Int, Int) -> Unit,
) -> Unit {
guard ui_element_by_id(id) is Some(trigger) else { return }
if context_menu_trigger_bindings.get(id) is Some(binding) &&
binding.element.is_same_node(trigger.as_node()) {
binding.open_at.val = open_at
return
}
let binding : ContextMenuTriggerBinding = {
element: trigger,
open_at: Ref(open_at),
}
context_menu_trigger_bindings[id] = binding
trigger.add_event_listener("contextmenu", event => {
if event.get_default_prevented() {
return
}
guard event.to_mouse_event() is Some(mouse) else { return }
event.prevent_default()
context_menu_open_from_point(
binding,
mouse.get_client_x().to_double(),
mouse.get_client_y().to_double(),
)
})
trigger.add_event_listener("keydown", event => {
if event.get_default_prevented() {
return
}
guard event.to_keyboard_event() is Some(keyboard) else { return }
let key = keyboard.key()
if key != "ContextMenu" && !(keyboard.shift_key() && key == "F10") {
return
}
event.prevent_default()
let rect = trigger.get_bounding_client_rect()
context_menu_open_from_point(
binding,
rect.get_left() + 8.0,
rect.get_top() + 8.0,
)
})
}
///|
#cfg(target="js")
fn show_context_menu(
id : String,
x : Int,
y : Int,
point_x : Int,
point_y : Int,
) -> Unit {
guard ui_element_by_id(id) is Some(popup) else { return }
guard popup.to_html_element() is Some(html) else { return }
context_menu_cancel_exit(id)
let trigger_id = popup.get_attribute("data-trigger-id").unwrap_or("")
let point = ui_element_by_id(context_menu_point_id(trigger_id))
if point is Some(point_element) &&
point_element.to_html_element() is Some(point_html) {
point_html.get_style().set_property("left", "\{point_x}px")
point_html.get_style().set_property("top", "\{point_y}px")
}
let style = html.get_style()
let position_area = style.get_property_value("position-area")
let css_anchored = point is Some(_) &&
@dom.CSS::supports("anchor-name", "--rui-anchor") &&
@dom.CSS::supports("position-anchor", "--rui-anchor") &&
@dom.CSS::supports("position-area", position_area) &&
@dom.CSS::supports("position-try-fallbacks", "flip-inline")
if css_anchored {
ignore(style.remove_property("--rui-floating-left"))
ignore(style.remove_property("--rui-floating-top"))
popup.set_attribute("data-positioning", "css-anchor")
popup.set_attribute("data-positioned", "true")
} else {
style.set_property("--rui-floating-left", "\{x.max(8)}px")
style.set_property("--rui-floating-top", "\{y.max(8)}px")
popup.set_attribute("data-positioning", "javascript-fallback")
}
popup.remove_attribute("hidden")
if floating_live_layer(id) is None {
bind_floating_sync(id, _ => ())
}
let already_open = if floating_live_layer(id) is Some(layer) {
floating_layer_is_open(layer)
} else {
false
}
if !already_open && ui_has_window() {
ignore(
@dom.window().get_computed_style(popup).get_property_value("opacity"),
)
}
ignore(set_floating_open(id, true))
// The zero-size point inside the trigger is the primary anchor. It keeps the
// pointer-relative placement attached during ancestor scrolling.
if css_anchored {
return
}
let rect = popup.get_bounding_client_rect()
let (viewport_left, viewport_top, viewport_width, viewport_height) = if ui_has_window() &&
@dom.window().get_visual_viewport() is Some(viewport) {
(
viewport.get_offset_left(),
viewport.get_offset_top(),
viewport.get_width(),
viewport.get_height(),
)
} else if @dom.document().get_document_element() is Some(root) {
(0.0, 0.0, root.get_client_width(), root.get_client_height())
} else {
(0.0, 0.0, 0.0, 0.0)
}
let clip_left = viewport_left + 8.0
let clip_top = viewport_top + 8.0
let clip_right = viewport_left + viewport_width - 8.0
let clip_bottom = viewport_top + viewport_height - 8.0
let left = x
.to_double()
.max(clip_left)
.min((clip_right - rect.get_width()).max(clip_left))
let top = y
.to_double()
.max(clip_top)
.min((clip_bottom - rect.get_height()).max(clip_top))
style.set_property("--rui-floating-left", "\{left.round().to_int()}px")
style.set_property("--rui-floating-top", "\{top.round().to_int()}px")
popup.set_attribute("data-positioned", "true")
}
///|
#cfg(target="js")
fn bind_context_menu_close(id : String, request_close : () -> Unit) -> Unit {
if ui_element_by_id(id) is Some(_) {
floating_set_request_close(id, request_close)
}
}
///|
#cfg(target="js")
priv struct ContextMenuExitBinding {
element : @dom.Element
listener : Ref[@dom.Listener?]
timeout : Ref[Int?]
}
///|
#cfg(target="js")
let context_menu_exit_bindings : Map[String, ContextMenuExitBinding] = Map([])
///|
#cfg(target="js")
fn context_menu_cancel_exit(id : String) -> Unit {
guard context_menu_exit_bindings.get(id) is Some(binding) else { return }
if binding.listener.val is Some(listener) {
binding.element.remove_event_listener("transitionend", listener)
binding.listener.val = None
}
if binding.timeout.val is Some(timeout) && ui_has_window() {
@dom.window().clear_timeout(timeout)
binding.timeout.val = None
}
}
///|
#cfg(target="js")
fn context_menu_css_time_ms(value : String) -> Double {
let text = "\{value.trim()}"
let milliseconds = text.has_suffix("ms")
let number_text = if milliseconds {
text.replace(old="ms", new="")
} else {
text.replace(old="s", new="")
}
let parsed = ui_parse_double_or(number_text, 0.0)
if milliseconds {
parsed
} else {
parsed * 1000.0
}
}
///|
#cfg(target="js")
fn context_menu_transition_duration(element : @dom.Element) -> Double {
guard ui_has_window() else { return 0.0 }
if @dom.window().match_media("(prefers-reduced-motion: reduce)").get_matches() {
return 0.0
}
let style = @dom.window().get_computed_style(element)
let durations : Array[Double] = []
let delays : Array[Double] = []
for duration in style.get_property_value("transition-duration").split(",") {
durations.push(context_menu_css_time_ms("\{duration}"))
}
for delay in style.get_property_value("transition-delay").split(",") {
delays.push(context_menu_css_time_ms("\{delay}"))
}
let mut total = 0.0
for index, duration in durations {
let delay = if delays.length() == 0 {
0.0
} else {
delays[index % delays.length()]
}
total = total.max(duration + delay)
}
total
}
///|
#cfg(target="js")
fn hide_context_menu_after_transition(id : String) -> Unit {
guard ui_element_by_id(id) is Some(popup) else { return }
context_menu_cancel_exit(id)
let binding : ContextMenuExitBinding = {
element: popup,
listener: Ref(None),
timeout: Ref(None),
}
context_menu_exit_bindings[id] = binding
fn finish() -> Unit {
if popup.get_attribute("data-state").unwrap_or("") == "open" {
return
}
context_menu_cancel_exit(id)
ignore(set_floating_open(id, false))
}
let duration = context_menu_transition_duration(popup)
if duration <= 0.0 || !ui_has_window() {
finish()
return
}
let listener : @dom.Listener = event => {
guard event.target().to_node() is Some(target) else { return }
if !popup.is_same_node(target) {
return
}
guard ui_transition_event(event) is Some(transition) else { return }
let property = transition.get_property_name()
if property == "opacity" || property == "transform" {
finish()
}
}
binding.listener.val = Some(listener)
popup.add_event_listener("transitionend", listener)
binding.timeout.val = Some(
@dom.window().set_timeout(finish, duration.ceil().to_int() + 34),
)
}
///|
#cfg(target="js")
fn context_bind_trigger_cmd(
trigger_id : String,
content_id : String,
open_at : @cmd.Emit[(Int, Int, Int, Int)],
) -> @cmd.Cmd {
@cmd.custom_cmd(kind=@cmd.AfterRender, scheduler => {
ui_after_mount(
trigger_id,
() => {
bind_context_menu_trigger(trigger_id, (x, y, point_x, point_y) => {
show_context_menu(content_id, x, y, point_x, point_y)
scheduler.add(open_at((x, y, point_x, point_y)))
})
},
purpose="context-menu-trigger-bind",
)
})
}
///|
#cfg(target="js")
fn context_bind_close_cmd(
content_id : String,
request_close : @cmd.Cmd,
) -> @cmd.Cmd {
@cmd.custom_cmd(kind=@cmd.AfterRender, scheduler => {
ui_after_mount(
content_id,
() => {
bind_context_menu_close(content_id, () => scheduler.add(request_close))
},
purpose="context-menu-close-bind",
)
})
}
///|
#cfg(target="js")
fn context_hide_after_transition_cmd(id : String) -> @cmd.Cmd {
@cmd.custom_cmd(kind=@cmd.AfterRender, _ => {
ui_after_mount(
id,
() => hide_context_menu_after_transition(id),
purpose="context-menu-exit-transition",
)
})
}
///|
#cfg(not(target="js"))
fn context_bind_close_cmd(
content_id : String,
request_close : @cmd.Cmd,
) -> @cmd.Cmd {
ignore((content_id, request_close))
@cmd.none
}
///|
#cfg(not(target="js"))
fn context_hide_after_transition_cmd(id : String) -> @cmd.Cmd {
ignore(id)
@cmd.none
}
///|
#cfg(target="js")
fn context_show_cmd(
id : String,
x : Int,
y : Int,
point_x : Int,
point_y : Int,
) -> @cmd.Cmd {
@cmd.custom_cmd(kind=@cmd.AfterRender, _ => {
ui_after_mount(
id,
() => show_context_menu(id, x, y, point_x, point_y),
purpose="context-menu-show",
)
})
}
///|
#cfg(not(target="js"))
fn context_show_cmd(
id : String,
x : Int,
y : Int,
point_x : Int,
point_y : Int,
) -> @cmd.Cmd {
ignore((id, x, y, point_x, point_y))
@cmd.none
}
///|
fn context_scope(
id : String,
model : MenuCoreModel,
point_x : Int,
point_y : Int,
set_open : @cmd.Emit[Bool],
toggle_checked : @cmd.Emit[String],
set_radio : @cmd.Emit[(String, String)],
) -> ContextMenuScope {
{
point_x,
point_y,
menu: {
id,
content_id: id + "-content",
trigger_id: id + "-trigger",
model,
counter: Ref(0),
close_command: set_open(false),
toggle_command: set_open(!model.open),
toggle_checked,
set_radio,
},
}
}
///|
/// Stateful context menu opened at the native `contextmenu` pointer position.
#cfg(target="js")
pub fn context_menu(
id~ : String,
default_open? : Bool = false,
default_checked_values? : Array[String] = [],
default_radio_values? : Array[(String, String)] = [],
on_open_change? : @cmd.Emit[Bool],
attrs? : @html.Attrs,
style? : Array[String] = [],
children : (ContextMenuScope) -> @html.Html,
) -> @rabbita.Val[@html.Html] {
let initial : ContextMenuModel = {
menu: menu_core_model(
default_open, default_checked_values, default_radio_values,
),
x: 8,
y: 8,
point_x: 0,
point_y: 0,
}
let (model, emit) = @rabbita.create_state_with_init(
init=emit => {
let set_active = emit.map(index => ContextSetActive(index))
let set_submenu = emit.map(value => ContextSetSubmenu(value))
(
initial,
@cmd.batch([
floating_bind_sync_cmd(
id + "-content",
emit.map(open => ContextNativeOpen(open)),
),
context_bind_close_cmd(id + "-content", emit(ContextSetOpen(false))),
context_bind_trigger_cmd(
id + "-trigger",
id + "-content",
emit.map(pair => ContextOpenAt(pair.0, pair.1, pair.2, pair.3)),
),
menu_bind_surface_cmd(
id + "-root",
set_active,
emit(ContextSetOpen(false)),
set_submenu,
),
if default_open {
context_show_cmd(id + "-content", 8, 8, 0, 0)
} else {
@cmd.none
},
]),
)
},
update=(emit, msg, current) => {
match msg {
ContextOpenAt(x, y, point_x, point_y) => {
let next = {
menu: { ..current.menu, open: true, active: -1, submenus: [] },
x,
y,
point_x,
point_y,
}
(
next,
@cmd.batch([
menu_position_submenus_cmd(id + "-root"),
menu_focus_first_cmd(
id + "-root",
emit.map(index => ContextSetActive(index)),
),
if current.menu.open {
@cmd.none
} else {
dropdown_notify(on_open_change, true)
},
]),
)
}
ContextSetOpen(open) =>
if open == current.menu.open {
(current, @cmd.none)
} else {
(
{ ..current, menu: { ..current.menu, open, submenus: [] } },
@cmd.batch([
if open {
context_show_cmd(
id + "-content",
current.x,
current.y,
current.point_x,
current.point_y,
)
} else {
@cmd.batch([
context_hide_after_transition_cmd(id + "-content"),
ui_focus_id(id + "-trigger"),
])
},
if open {
@cmd.none
} else {
menu_position_submenus_cmd(id + "-root")
},
dropdown_notify(on_open_change, open),
]),
)
}
ContextNativeOpen(open) =>
if open == current.menu.open {
(current, @cmd.none)
} else {
(
{ ..current, menu: { ..current.menu, open, submenus: [] } },
@cmd.batch([
if open {
@cmd.none
} else {
menu_position_submenus_cmd(id + "-root")
},
dropdown_notify(on_open_change, open),
]),
)
}
ContextSetActive(index) =>
if current.menu.active == index {
(current, @cmd.none)
} else {
(
{ ..current, menu: { ..current.menu, active: index } },
menu_focus_item_cmd(id + "-root", index),
)
}
ContextToggleChecked(value) =>
(
{
..current,
menu: {
..current.menu,
checked: menu_toggle_value(current.menu.checked, value),
},
},
@cmd.none,
)
ContextSetRadio(group, value) =>
(
{
..current,
menu: {
..current.menu,
radios: menu_set_radio_value(current.menu.radios, group, value),
},
},
@cmd.none,
)
ContextSetSubmenu(change) => {
let submenus = menu_update_submenus(current.menu.submenus, change)
if submenus == current.menu.submenus {
(current, @cmd.none)
} else {
(
{ ..current, menu: { ..current.menu, submenus, } },
menu_position_submenus_cmd(id + "-root"),
)
}
}
}
},
)
model.view(model => {
let scope = context_scope(
id,
model.menu,
model.point_x,
model.point_y,
emit.map(open => ContextSetOpen(open)),
emit.map(value => ContextToggleChecked(value)),
emit.map(pair => ContextSetRadio(pair.0, pair.1)),
)
menu_root_surface(
"context-menu",
id + "-root",
model.menu.open,
attrs,
style,
children(scope),
)
})
}
///|
#cfg(not(target="js"))
pub fn context_menu(
id~ : String,
default_open? : Bool = false,
default_checked_values? : Array[String] = [],
default_radio_values? : Array[(String, String)] = [],
on_open_change? : @cmd.Emit[Bool],
attrs? : @html.Attrs,
style? : Array[String] = [],
children : (ContextMenuScope) -> @html.Html,
) -> @rabbita.Val[@html.Html] {
ignore(on_open_change)
let scope = context_scope(
id,
menu_core_model(default_open, default_checked_values, default_radio_values),
0,
0,
menu_noop_emit(),
menu_noop_emit(),
menu_noop_emit(),
)
@rabbita.Val::constant(
menu_root_surface(
"context-menu",
id + "-root",
default_open,
attrs,
style,
children(scope),
),
)
}
///|
/// Explicit Root spelling matching the compound-component API.
pub fn context_menu_root(
id~ : String,
default_open? : Bool = false,
default_checked_values? : Array[String] = [],
default_radio_values? : Array[(String, String)] = [],
on_open_change? : @cmd.Emit[Bool],
attrs? : @html.Attrs,
style? : Array[String] = [],
children : (ContextMenuScope) -> @html.Html,
) -> @rabbita.Val[@html.Html] {
context_menu(
id~,
default_open~,
default_checked_values~,
default_radio_values~,
on_open_change?,
attrs?,
style~,
children,
)
}
///|
/// Zero-DOM Portal adapter for shadcn-compatible compound composition.
/// Content already uses the native Popover top layer, so this adapter adds no
/// wrapper element and requires no registration or portal runtime.
pub fn context_menu_portal(children : Array[@html.Html]) -> @html.Html {
@html.fragment(children)
}
///|
pub fn[C : @html.IsChildren] context_menu_trigger(
scope : ContextMenuScope,
tabindex? : Int = 0,
class? : String,
title? : String,
attrs? : @html.Attrs,
style? : Array[String] = [],
children : C,
) -> @html.Html {
@html.div(
style=ui_styles([UiBoxSizing, "position:relative"], style),
id=scope.menu.trigger_id,
class?,
title?,
attrs=menu_state_attrs(attrs, "context-menu-trigger", scope.menu.model.open)
.tabindex(tabindex)
.aria_haspopup("menu")
.aria_expanded(ui_bool(scope.menu.model.open))
.aria_controls(scope.menu.content_id),
[
@html.div(style=[UiBoxSizing, "display:contents"], children),
@html.span(
style=[
UiBoxSizing,
ContextMenuPointStyle,
"left:\{scope.point_x}px;top:\{scope.point_y}px",
popup_anchor_style(context_menu_point_id(scope.menu.trigger_id)),
],
id=context_menu_point_id(scope.menu.trigger_id),
attrs=@html.Attrs::build()
.data_set("slot", "context-menu-anchor")
.aria_hidden("true"),
@html.nothing,
),
],
)
}
///|
pub fn[C : @html.IsChildren] context_menu_content(
scope : ContextMenuScope,
aria_label? : String,
class? : String,
title? : String,
attrs? : @html.Attrs,
style? : Array[String] = [],
children : C,
) -> @html.Html {
menu_content_surface(
"context-menu-content",
scope.menu,
Bottom,
Start,
0,
0,
aria_label,
class,
title,
attrs,
style,
children,
)
}
///|
pub fn[C : @html.IsChildren] context_menu_group(
aria_label? : String,
class? : String,
title? : String,
attrs? : @html.Attrs,
style? : Array[String] = [],
children : C,
) -> @html.Html {
menu_group_surface(
"context-menu-group", aria_label, class, title, attrs, style, children,
)
}
///|
pub fn[C : @html.IsChildren] context_menu_item(
scope : ContextMenuScope,
disabled? : Bool = false,
inset? : Bool = false,
destructive? : Bool = false,
close_on_select? : Bool = true,
on_select? : @cmd.Cmd,
aria_label? : String,
class? : String,
title? : String,
attrs? : @html.Attrs,
style? : Array[String] = [],
children : C,
) -> @html.Html {
menu_item_surface(
"context-menu-item",
scope.menu,
"menuitem",
None,
MenuIndicatorInlineStart,
disabled,
inset,
destructive,
None,
close_on_select,
None,
on_select,
aria_label,
class,
title,
attrs,
style,
children,
)
}
///|
pub fn[C : @html.IsChildren] context_menu_checkbox_item(
scope : ContextMenuScope,
value~ : String,
disabled? : Bool = false,
close_on_select? : Bool = false,
on_checked_change? : @cmd.Emit[Bool],
on_select? : @cmd.Cmd,
aria_label? : String,
class? : String,
title? : String,
attrs? : @html.Attrs,
style? : Array[String] = [],
children : C,
) -> @html.Html {
let checked = menu_contains(scope.menu.model.checked, value)
let notify = if on_checked_change is Some(notify) {
notify(!checked)
} else {
@cmd.none
}
menu_item_surface(
"context-menu-checkbox-item",
scope.menu,
"menuitemcheckbox",
Some(checked),
MenuIndicatorInlineEnd,
disabled,
false,
false,
None,
close_on_select,
Some(@cmd.batch([notify, (scope.menu.toggle_checked)(value)])),
on_select,
aria_label,
class,
title,
attrs,
style,
children,
)
}
///|
pub fn[C : @html.IsChildren] context_menu_radio_group(
scope : ContextMenuScope,
value~ : String,
aria_label? : String,
class? : String,
title? : String,
attrs? : @html.Attrs,
style? : Array[String] = [],
children : (ContextMenuRadioGroupScope) -> C,
) -> @html.Html {
menu_group_surface(
"context-menu-radio-group",
aria_label,
class,
title,
attrs,
style,
children({ menu: scope.menu, group: value }),
)
}
///|
pub fn[C : @html.IsChildren] context_menu_radio_item(
scope : ContextMenuRadioGroupScope,
value~ : String,
disabled? : Bool = false,
close_on_select? : Bool = false,
on_value_change? : @cmd.Emit[String],
on_select? : @cmd.Cmd,
aria_label? : String,
class? : String,
title? : String,
attrs? : @html.Attrs,
style? : Array[String] = [],
children : C,
) -> @html.Html {
let checked = menu_radio_value(scope.menu.model.radios, scope.group) ==
Some(value)
let notify = if on_value_change is Some(notify) {
notify(value)
} else {
@cmd.none
}
menu_item_surface(
"context-menu-radio-item",
scope.menu,
"menuitemradio",
Some(checked),
MenuIndicatorInlineEnd,
disabled,
false,
false,
None,
close_on_select,
Some(@cmd.batch([notify, (scope.menu.set_radio)((scope.group, value))])),
on_select,
aria_label,
class,
title,
attrs,
style,
children,
)
}
///|
pub fn[C : @html.IsChildren] context_menu_label(
inset? : Bool = false,
class? : String,
title? : String,
attrs? : @html.Attrs,
style? : Array[String] = [],
children : C,
) -> @html.Html {
menu_label_surface(
"context-menu-label", inset, class, title, attrs, style, children,
)
}
///|
pub fn context_menu_separator(
class? : String,
attrs? : @html.Attrs,
style? : Array[String] = [],
) -> @html.Html {
menu_separator_surface("context-menu-separator", class, attrs, style)
}
///|
pub fn[C : @html.IsChildren] context_menu_shortcut(
class? : String,
title? : String,
attrs? : @html.Attrs,
style? : Array[String] = [],
children : C,
) -> @html.Html {
menu_shortcut_surface(
"context-menu-shortcut", class, title, attrs, style, children,
)
}
///|
pub fn[C : @html.IsChildren] context_menu_sub(
scope : ContextMenuScope,
value~ : String,
attrs? : @html.Attrs,
style? : Array[String] = [],
children : (ContextMenuSubScope) -> C,
) -> @html.Html {
let key = menu_submenu_key(scope.menu, value)
let open = menu_submenu_open(scope.menu.model.submenus, key)
menu_sub_root_surface(
"context-menu-sub",
open,
attrs,
style,
children({ menu: scope.menu, key }),
)
}
///|
pub fn[C : @html.IsChildren] context_menu_sub_trigger(
scope : ContextMenuSubScope,
disabled? : Bool = false,
inset? : Bool = false,
aria_label? : String,
class? : String,
title? : String,
attrs? : @html.Attrs,
style? : Array[String] = [],
children : C,
) -> @html.Html {
menu_item_surface(
"context-menu-sub-trigger",
scope.menu,
"menuitem",
None,
MenuIndicatorInlineStart,
disabled,
inset,
false,
Some(scope.key),
false,
None,
None,
aria_label,
class,
title,
attrs,
style,
children,
)
}
///|
pub fn[C : @html.IsChildren] context_menu_sub_content(
scope : ContextMenuSubScope,
class? : String,
title? : String,
attrs? : @html.Attrs,
style? : Array[String] = [],
children : C,
) -> @html.Html {
menu_sub_content_surface(
"context-menu-sub-content",
scope.key,
menu_submenu_open(scope.menu.model.submenus, scope.key),
class,
title,
attrs,
style,
children,
)
}