///|
/// APG Dialog (Modal) Pattern
/// https://www.w3.org/WAI/ARIA/apg/patterns/dialog-modal/
///
/// A dialog is a window overlaid on the primary window or another dialog.
/// Modal dialogs prevent interaction with content outside.
///
/// Keyboard Interaction:
/// - Tab: Move focus to next element inside dialog (trapped)
/// - Shift+Tab: Move focus to previous element inside dialog (trapped)
/// - Escape: Close the dialog
///
/// ARIA:
/// - role="dialog" with aria-modal="true"
/// - aria-labelledby or aria-label (required)
/// - aria-describedby (optional)
///|
/// Create a modal dialog container.
///
/// Parameters:
/// - aria_label: Accessible label for the dialog
/// - aria_labelledby: ID of the title element
/// - aria_describedby: ID of the description element
/// - open: Whether the dialog is currently open
/// - on_keydown: Keydown handler (for Escape key to close)
/// - children: Dialog content
///
/// Example:
/// ```
/// dialog(
/// aria_labelledby="dialog-title",
/// open=true,
/// [
/// h2(id="dialog-title", [text("Confirm")]),
/// p([text("Are you sure?")]),
/// button([text("Yes")]),
/// button([text("No")])
/// ]
/// )
/// ```
pub fn[E] dialog(
aria_label? : String,
aria_labelledby? : String,
aria_describedby? : String,
open : Bool,
on_keydown? : @luna.EventHandler[E],
children : Array[@luna.Node[E, String]],
) -> @luna.Node[E, String] {
let attrs : Array[(String, @luna.Attr[E, String])] = [
("role", @luna.attr_static("dialog")),
("aria-modal", @luna.attr_static("true")),
("tabindex", @luna.attr_static("-1")), // Allow focus for keyboard events
]
if aria_label is Some(label) {
attrs.push(("aria-label", @luna.attr_static(label)))
}
if aria_labelledby is Some(id) {
attrs.push(("aria-labelledby", @luna.attr_static(id)))
}
if aria_describedby is Some(id) {
attrs.push(("aria-describedby", @luna.attr_static(id)))
}
if on_keydown is Some(handler) {
attrs.push(("keydown", @luna.attr_handler(handler)))
}
if !open {
attrs.push(("hidden", @luna.attr_static("")))
}
@luna.h("div", attrs) <| children
}
///|
/// Create a dynamic dialog with signal-based open state.
pub fn[E] dialog_dyn(
aria_label? : String,
aria_labelledby? : String,
aria_describedby? : String,
open : @resource.Signal[Bool],
on_keydown? : @luna.EventHandler[E],
children : Array[@luna.Node[E, String]],
) -> @luna.Node[E, String] {
let attrs : Array[(String, @luna.Attr[E, String])] = [
("role", @luna.attr_static("dialog")),
("aria-modal", @luna.attr_static("true")),
("tabindex", @luna.attr_static("-1")), // Allow focus for keyboard events
]
if aria_label is Some(label) {
attrs.push(("aria-label", @luna.attr_static(label)))
}
if aria_labelledby is Some(id) {
attrs.push(("aria-labelledby", @luna.attr_static(id)))
}
if aria_describedby is Some(id) {
attrs.push(("aria-describedby", @luna.attr_static(id)))
}
if on_keydown is Some(handler) {
attrs.push(("keydown", @luna.attr_handler(handler)))
}
@luna.show(fn() { open.get() }, fn() { @luna.h("div", attrs) <| children })
}
///|
/// Create a native HTML dialog element.
/// Uses