///|
/// Focus management utilities for accessibility-based Tab navigation
///|
/// Focusable element with its tab index and element id
pub struct FocusableElement {
id : String
tab_index : Int
}
///|
/// Focus context for managing Tab navigation
pub struct FocusContext {
elements : Array[FocusableElement]
mut current_index : Int
}
///|
/// Create a new focus context
pub fn FocusContext::new() -> FocusContext {
{ elements: [], current_index: -1 }
}
///|
/// Register a focusable element
/// Elements are sorted by tab_index when calculating focus order
pub fn FocusContext::register(
self : FocusContext,
id : String,
tab_index : Int,
) -> Unit {
// Only register if tab_index >= 0 (-1 means not focusable)
if tab_index >= 0 {
self.elements.push({ id, tab_index })
}
}
///|
/// Clear all registered elements (call before re-rendering)
pub fn FocusContext::clear(self : FocusContext) -> Unit {
// Reset to empty state
let _ = self.elements.drain(0, self.elements.length())
self.current_index = -1
}
///|
/// Get the sorted list of focusable elements by tab_index
/// Tab order: 0 comes first (in document order), then 1, 2, 3, ...
fn FocusContext::sorted_elements(
self : FocusContext,
) -> Array[FocusableElement] {
if self.elements.length() == 0 {
return []
}
// Stable sort by tab_index
let sorted = self.elements.copy()
sorted.sort_by(fn(a, b) { a.tab_index - b.tab_index })
sorted
}
///|
/// Move focus to the next element (Tab key)
/// Returns the id of the newly focused element, or None if no focusable elements
pub fn FocusContext::focus_next(self : FocusContext) -> String? {
let sorted = self.sorted_elements()
if sorted.length() == 0 {
return None
}
self.current_index = (self.current_index + 1) % sorted.length()
Some(sorted[self.current_index].id)
}
///|
/// Move focus to the previous element (Shift+Tab key)
/// Returns the id of the newly focused element, or None if no focusable elements
pub fn FocusContext::focus_prev(self : FocusContext) -> String? {
let sorted = self.sorted_elements()
if sorted.length() == 0 {
return None
}
self.current_index = if self.current_index <= 0 {
sorted.length() - 1
} else {
self.current_index - 1
}
Some(sorted[self.current_index].id)
}
///|
/// Set focus to a specific element by id
/// Returns true if the element was found and focused
pub fn FocusContext::focus_by_id(self : FocusContext, id : String) -> Bool {
let sorted = self.sorted_elements()
for i, elem in sorted {
if elem.id == id {
self.current_index = i
return true
}
}
false
}
///|
/// Get the currently focused element id
pub fn FocusContext::current_focus(self : FocusContext) -> String? {
let sorted = self.sorted_elements()
if self.current_index >= 0 && self.current_index < sorted.length() {
Some(sorted[self.current_index].id)
} else {
None
}
}
///|
/// Get all focusable element ids in tab order
pub fn FocusContext::focusable_ids(self : FocusContext) -> Array[String] {
self.sorted_elements().map(fn(e) { e.id })
}
///|
/// Check if an element is focusable
pub fn FocusContext::is_focusable(self : FocusContext, id : String) -> Bool {
for elem in self.elements {
if elem.id == id {
return true
}
}
false
}
///|
/// Get the number of focusable elements
pub fn FocusContext::count(self : FocusContext) -> Int {
self.elements.length()
}
///|
/// Extract tab_index from TuiAttrValue if present
pub fn extract_tab_index(attrs : Array[(String, TuiAttr)]) -> Int? {
for attr in attrs {
let (key, value) = attr
if key == "tab_index" {
match value {
@luna.Attr::VStatic(TuiAttrValue::TabIndex(v)) => return Some(v)
@luna.Attr::VDynamic(getter) =>
match getter() {
TuiAttrValue::TabIndex(v) => return Some(v)
_ => continue
}
_ => continue
}
}
}
None
}