///|
/// Headless component types and state management
/// Pure logic without styling - follows APG (ARIA Authoring Practices) patterns
// =============================================================================
// Button State
// =============================================================================
///|
/// Button state enum
pub(all) enum ButtonState {
Default
Hovered
Focused
Pressed
Disabled
} derive(Debug, Eq)
///|
pub impl Show for ButtonState with fn output(
self : ButtonState,
logger : &Logger,
) -> Unit {
self.to_repr().output(logger)
}
///|
/// Check if button is interactive
pub fn ButtonState::is_interactive(self : ButtonState) -> Bool {
match self {
Disabled => false
_ => true
}
}
// =============================================================================
// Input State
// =============================================================================
///|
/// Input field state
pub(all) enum InputState {
Idle
Focused
Editing
Disabled
}
///|
/// Check if input is editable
pub fn InputState::is_editable(self : InputState) -> Bool {
match self {
Editing => true
_ => false
}
}
///|
/// Check if input can receive focus
pub fn InputState::is_focusable(self : InputState) -> Bool {
match self {
Disabled => false
_ => true
}
}
// =============================================================================
// Toggle State (Checkbox, Switch, Radio)
// =============================================================================
///|
/// Toggle state for checkbox/switch
pub(all) struct ToggleState {
checked : Bool
focused : Bool
disabled : Bool
}
///|
/// Create default toggle state
pub fn ToggleState::new(checked? : Bool = false) -> ToggleState {
{ checked, focused: false, disabled: false }
}
///|
/// Toggle the checked state
pub fn ToggleState::toggle(self : ToggleState) -> ToggleState {
if self.disabled {
self
} else {
{ ..self, checked: !self.checked }
}
}
///|
/// Set focus state
pub fn ToggleState::set_focused(
self : ToggleState,
focused : Bool,
) -> ToggleState {
{ ..self, focused, }
}
// =============================================================================
// Selection State (Listbox, Combobox, Tabs)
// =============================================================================
///|
/// Selection state for single-select components
pub(all) struct SelectionState {
selected_id : String
focused_id : String
open : Bool
}
///|
/// Create default selection state
pub fn SelectionState::new(selected_id? : String = "") -> SelectionState {
{ selected_id, focused_id: selected_id, open: false }
}
///|
/// Select an item
pub fn SelectionState::select(
self : SelectionState,
id : String,
) -> SelectionState {
{ ..self, selected_id: id, focused_id: id }
}
///|
/// Focus an item (for keyboard navigation)
pub fn SelectionState::focus(
self : SelectionState,
id : String,
) -> SelectionState {
{ ..self, focused_id: id }
}
///|
/// Toggle open state (for dropdown-style components)
pub fn SelectionState::toggle_open(self : SelectionState) -> SelectionState {
{ ..self, open: !self.open }
}
// =============================================================================
// Modal State
// =============================================================================
///|
/// Modal dialog state
pub(all) struct ModalState {
open : Bool
focused_element : String
}
///|
/// Create closed modal state
pub fn ModalState::new() -> ModalState {
{ open: false, focused_element: "" }
}
///|
/// Open the modal
pub fn ModalState::show(self : ModalState) -> ModalState {
{ ..self, open: true }
}
///|
/// Close the modal
pub fn ModalState::hide(self : ModalState) -> ModalState {
{ ..self, open: false }
}
///|
/// Check if modal is open
pub fn ModalState::is_open(self : ModalState) -> Bool {
self.open
}
// =============================================================================
// Accordion State
// =============================================================================
///|
/// Accordion panel state
pub(all) struct AccordionState {
expanded_ids : Array[String]
allow_multiple : Bool
}
///|
/// Create accordion state
pub fn AccordionState::new(allow_multiple? : Bool = false) -> AccordionState {
{ expanded_ids: [], allow_multiple }
}
///|
/// Check if a panel is expanded
pub fn AccordionState::is_expanded(self : AccordionState, id : String) -> Bool {
self.expanded_ids.contains(id)
}
///|
/// Toggle a panel's expanded state
pub fn AccordionState::toggle(
self : AccordionState,
id : String,
) -> AccordionState {
if self.is_expanded(id) {
// Collapse
let new_ids = self.expanded_ids.filter(fn(x) { x != id })
{ ..self, expanded_ids: new_ids }
// Expand
} else if self.allow_multiple {
let new_ids = self.expanded_ids.copy()
new_ids.push(id)
{ ..self, expanded_ids: new_ids }
} else {
{ ..self, expanded_ids: [id] }
}
}
// =============================================================================
// Toast State
// =============================================================================
///|
/// Toast notification severity level
pub(all) enum ToastLevel {
Info
Success
Warning
Error
}
///|
/// Toast notification state
pub(all) struct ToastState {
message : String
level : ToastLevel
visible : Bool
}
///|
/// Create a new toast state
pub fn ToastState::new(
message : String,
level? : ToastLevel = Info,
) -> ToastState {
{ message, level, visible: true }
}
///|
/// Dismiss the toast
pub fn ToastState::dismiss(self : ToastState) -> ToastState {
{ ..self, visible: false }
}
// =============================================================================
// Grid Select State
// =============================================================================
///|
/// 2D grid selection state
pub(all) struct GridSelectState {
columns : Int
selected : Int
total : Int
}
///|
/// Create a new grid select state
pub fn GridSelectState::new(
columns : Int,
total : Int,
selected? : Int = 0,
) -> GridSelectState {
{ columns, total, selected }
}
///|
/// Move selection right
pub fn GridSelectState::move_right(self : GridSelectState) -> GridSelectState {
let next = self.selected + 1
if next < self.total {
{ ..self, selected: next }
} else {
self
}
}
///|
/// Move selection left
pub fn GridSelectState::move_left(self : GridSelectState) -> GridSelectState {
if self.selected > 0 {
{ ..self, selected: self.selected - 1 }
} else {
self
}
}
///|
/// Move selection down
pub fn GridSelectState::move_down(self : GridSelectState) -> GridSelectState {
let next = self.selected + self.columns
if next < self.total {
{ ..self, selected: next }
} else {
self
}
}
///|
/// Move selection up
pub fn GridSelectState::move_up(self : GridSelectState) -> GridSelectState {
let next = self.selected - self.columns
if next >= 0 {
{ ..self, selected: next }
} else {
self
}
}
///|
/// Get row and column of current selection
pub fn GridSelectState::position(self : GridSelectState) -> (Int, Int) {
(self.selected / self.columns, self.selected % self.columns)
}
// =============================================================================
// Tree State
// =============================================================================
///|
/// Tree view state
pub(all) struct TreeState {
expanded_ids : Array[String]
selected_id : String
focused_id : String
}
///|
/// Create a new tree state
pub fn TreeState::new() -> TreeState {
{ expanded_ids: [], selected_id: "", focused_id: "" }
}
///|
/// Toggle expand/collapse for a node
pub fn TreeState::toggle_expand(self : TreeState, id : String) -> TreeState {
if self.expanded_ids.contains(id) {
let new_ids = self.expanded_ids.filter(fn(x) { x != id })
{ ..self, expanded_ids: new_ids }
} else {
let new_ids = self.expanded_ids.copy()
new_ids.push(id)
{ ..self, expanded_ids: new_ids }
}
}
///|
/// Check if a node is expanded
pub fn TreeState::is_expanded(self : TreeState, id : String) -> Bool {
self.expanded_ids.contains(id)
}
///|
/// Select a node
pub fn TreeState::select(self : TreeState, id : String) -> TreeState {
{ ..self, selected_id: id, focused_id: id }
}
///|
/// Focus a node
pub fn TreeState::focus(self : TreeState, id : String) -> TreeState {
{ ..self, focused_id: id }
}
// =============================================================================
// Focus Navigation
// =============================================================================
///|
/// Focus navigation helper for keyboard navigation
pub(all) struct FocusNav {
ids : Array[String]
current_index : Int
}
///|
/// Create focus navigation
pub fn FocusNav::new(ids : Array[String]) -> FocusNav {
{ ids, current_index: 0 }
}
///|
/// Get current focused ID
pub fn FocusNav::current(self : FocusNav) -> String {
if self.ids.length() == 0 {
""
} else {
self.ids[self.current_index]
}
}
///|
/// Move focus to next item
pub fn FocusNav::next(self : FocusNav) -> FocusNav {
if self.ids.length() == 0 {
self
} else {
let next_index = (self.current_index + 1) % self.ids.length()
{ ..self, current_index: next_index }
}
}
///|
/// Move focus to previous item
pub fn FocusNav::prev(self : FocusNav) -> FocusNav {
if self.ids.length() == 0 {
self
} else {
let prev_index = if self.current_index == 0 {
self.ids.length() - 1
} else {
self.current_index - 1
}
{ ..self, current_index: prev_index }
}
}
///|
/// Focus specific ID
pub fn FocusNav::focus_id(self : FocusNav, id : String) -> FocusNav {
for i, item in self.ids {
if item == id {
return { ..self, current_index: i }
}
}
self
}
///|
/// Check if ID is focused
pub fn FocusNav::is_focused(self : FocusNav, id : String) -> Bool {
self.current() == id
}
// =============================================================================
// Button State Resolution
// =============================================================================
///|
/// Resolve button state from hover_id and optional FocusNav
pub fn resolve_button_state(
id : String,
hover_id : String,
focus? : FocusNav? = None,
disabled? : Bool = false,
) -> ButtonState {
if disabled {
return Disabled
}
if hover_id == id {
return Hovered
}
match focus {
Some(nav) => if nav.is_focused(id) { return Focused }
None => ()
}
Default
}