///|
/// APG Checkbox Pattern
/// https://www.w3.org/WAI/ARIA/apg/patterns/checkbox/
///
/// Checkbox supports two types:
/// - Dual-state: checked / not checked
/// - Tri-state: checked / not checked / partially checked (mixed)
///
/// Keyboard Interaction:
/// - Space: Toggle the checkbox state
///
/// ARIA:
/// - role="checkbox"
/// - aria-checked: "true" / "false" / "mixed"
///|
/// Checkbox state
pub(all) enum CheckboxState {
Checked // aria-checked="true"
Unchecked // aria-checked="false"
Mixed // aria-checked="mixed" (tri-state)
}
///|
fn CheckboxState::to_aria(self : CheckboxState) -> String {
match self {
Checked => "true"
Unchecked => "false"
Mixed => "mixed"
}
}
///|
/// Create an accessible checkbox.
///
/// Parameters:
/// - checked: Checkbox state
/// - disabled: Whether disabled
/// - aria_label: Accessible label
/// - aria_labelledby: ID of labelling element
/// - on_click: Click handler
/// - on_keydown: Keydown handler (for Space key toggle)
/// - children: Checkbox label content
pub fn[E] checkbox(
checked : CheckboxState,
disabled? : Bool,
aria_label? : String,
aria_labelledby? : String,
on_click? : @luna.EventHandler[E],
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("checkbox")),
("aria-checked", @luna.attr_static(checked.to_aria())),
("tabindex", @luna.attr_static("0")),
]
if disabled is Some(true) {
attrs.push(("aria-disabled", @luna.attr_static("true")))
}
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 on_click is Some(handler) {
attrs.push(("click", @luna.attr_handler(handler)))
}
if on_keydown is Some(handler) {
attrs.push(("keydown", @luna.attr_handler(handler)))
}
@luna.h("div", attrs) <| children
}
///|
/// Create a dynamic checkbox with signal-based state.
pub fn[E] checkbox_dyn(
checked : @resource.Signal[CheckboxState],
disabled? : Bool,
aria_label? : String,
on_click? : @luna.EventHandler[E],
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("checkbox")),
("aria-checked", @luna.attr_dynamic(fn() { checked.get().to_aria() })),
("tabindex", @luna.attr_static("0")),
]
if disabled is Some(true) {
attrs.push(("aria-disabled", @luna.attr_static("true")))
}
if aria_label is Some(label) {
attrs.push(("aria-label", @luna.attr_static(label)))
}
if on_click is Some(handler) {
attrs.push(("click", @luna.attr_handler(handler)))
}
if on_keydown is Some(handler) {
attrs.push(("keydown", @luna.attr_handler(handler)))
}
@luna.h("div", attrs) <| children
}
///|
/// Create a checkbox with computed state (accepts getter function).
/// Useful for derived checkbox states.
pub fn[E] checkbox_computed(
checked : () -> CheckboxState,
disabled? : Bool,
aria_label? : String,
on_click? : @luna.EventHandler[E],
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("checkbox")),
("aria-checked", @luna.attr_dynamic(fn() { checked().to_aria() })),
("tabindex", @luna.attr_static("0")),
]
if disabled is Some(true) {
attrs.push(("aria-disabled", @luna.attr_static("true")))
}
if aria_label is Some(label) {
attrs.push(("aria-label", @luna.attr_static(label)))
}
if on_click is Some(handler) {
attrs.push(("click", @luna.attr_handler(handler)))
}
if on_keydown is Some(handler) {
attrs.push(("keydown", @luna.attr_handler(handler)))
}
@luna.h("div", attrs) <| children
}
///|
/// Create a native HTML checkbox input with label.
pub fn[E] checkbox_native(
name : String,
checked : Bool,
disabled? : Bool,
id? : String,
children : Array[@luna.Node[E, String]],
) -> @luna.Node[E, String] {
let input_id = id.unwrap_or("checkbox-" + name)
let input_attrs : Array[(String, @luna.Attr[E, String])] = [
("type", @luna.attr_static("checkbox")),
("name", @luna.attr_static(name)),
("id", @luna.attr_static(input_id)),
]
if checked {
input_attrs.push(("checked", @luna.attr_static("")))
}
if disabled is Some(true) {
input_attrs.push(("disabled", @luna.attr_static("")))
}
let label_attrs : Array[(String, @luna.Attr[E, String])] = [
("for", @luna.attr_static(input_id)),
]
@luna.fragment <| [
@luna.h("input", input_attrs) <| [],
@luna.h("label", label_attrs) <| children,
]
}
///|
/// Create a checkbox group with role="group".
pub fn[E] checkbox_group(
aria_label? : String,
aria_labelledby? : String,
children : Array[@luna.Node[E, String]],
) -> @luna.Node[E, String] {
let attrs : Array[(String, @luna.Attr[E, String])] = [
("role", @luna.attr_static("group")),
]
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)))
}
@luna.h("div", attrs) <| children
}