///|
/// FocusNav - Signal-based focus navigation utility
/// Simplifies focus management in forms and interactive components
///|
/// Focus navigation state with signal-based tracking
pub(all) struct FocusNav {
focused_id : @signals.Signal[String]
ids : Array[String]
}
///|
/// Create a new focus navigation helper
pub fn FocusNav::new(ids : Array[String]) -> FocusNav {
{ focused_id: @signals.signal(""), ids }
}
///|
/// Create with initial focus
pub fn FocusNav::with_initial(
ids : Array[String],
initial : String,
) -> FocusNav {
{ focused_id: @signals.signal(initial), ids }
}
///|
/// Get the currently focused ID (empty string if none)
pub fn FocusNav::get(self : FocusNav) -> String {
self.focused_id.get()
}
///|
/// Set focus to a specific ID
pub fn FocusNav::set(self : FocusNav, id : String) -> Unit {
self.focused_id.set(id)
}
///|
/// Clear focus
pub fn FocusNav::blur(self : FocusNav) -> Unit {
self.focused_id.set("")
}
///|
/// Check if an ID is currently focused
pub fn FocusNav::is_focused(self : FocusNav, id : String) -> Bool {
self.focused_id.get() == id
}
///|
/// Move focus to the next item (wraps around)
pub fn FocusNav::next(self : FocusNav) -> Unit {
let current = self.focused_id.get()
if self.ids.length() == 0 {
return
}
if current == "" {
self.focused_id.set(self.ids[0])
return
}
match self.ids.search(current) {
Some(idx) => {
let next_idx = (idx + 1) % self.ids.length()
self.focused_id.set(self.ids[next_idx])
}
None => self.focused_id.set(self.ids[0])
}
}
///|
/// Move focus to the previous item (wraps around)
pub fn FocusNav::prev(self : FocusNav) -> Unit {
let current = self.focused_id.get()
if self.ids.length() == 0 {
return
}
if current == "" {
self.focused_id.set(self.ids[self.ids.length() - 1])
return
}
match self.ids.search(current) {
Some(idx) => {
let prev_idx = if idx == 0 { self.ids.length() - 1 } else { idx - 1 }
self.focused_id.set(self.ids[prev_idx])
}
None => self.focused_id.set(self.ids[self.ids.length() - 1])
}
}
///|
/// Get InputState for an input component
pub fn FocusNav::input_state(self : FocusNav, id : String) -> InputState {
if self.is_focused(id) {
InputState::Focused
} else {
InputState::Idle
}
}
///|
/// Get TextareaState for a textarea component
pub fn FocusNav::textarea_state(self : FocusNav, id : String) -> TextareaState {
if self.is_focused(id) {
TextareaState::Focused
} else {
TextareaState::Idle
}
}
///|
/// Get ButtonState for a button component
pub fn FocusNav::button_state(self : FocusNav, id : String) -> ButtonState {
if self.is_focused(id) {
ButtonState::Hover
} else {
ButtonState::Normal
}
}
///|
/// Check if the focused ID is in a specific list
pub fn FocusNav::focused_in(self : FocusNav, ids : Array[String]) -> Bool {
ids.contains(self.focused_id.get())
}
///|
/// Handle Tab/Shift+Tab navigation events
/// Returns true if the event was handled
pub fn FocusNav::handle_tab(
self : FocusNav,
event : @events.InputEvent,
) -> Bool {
if event.is_tab() {
self.next()
return true
}
if event.is_backtab() {
self.prev()
return true
}
false
}
///|
/// Handle arrow key navigation (Up/Down)
/// Returns true if the event was handled
pub fn FocusNav::handle_arrows(
self : FocusNav,
event : @events.InputEvent,
) -> Bool {
match event.is_arrow() {
Some(@events.SpecialKey::Down) => {
self.next()
true
}
Some(@events.SpecialKey::Up) => {
self.prev()
true
}
_ => false
}
}