use-focus 0.1.0

Focus metadata primitives for RustUse UI
Documentation
#![forbid(unsafe_code)]
#![doc = include_str!("../README.md")]

/// Focus state metadata.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Ord, PartialOrd)]
pub enum FocusState {
    Unfocused,
    Focused,
    FocusVisible,
    Disabled,
}

impl FocusState {
    pub fn has_focus(self) -> bool {
        matches!(self, Self::Focused | Self::FocusVisible)
    }
}

/// A focus target identifier.
#[derive(Debug, Clone, PartialEq, Eq, Hash, Ord, PartialOrd)]
pub struct FocusTarget(String);

impl FocusTarget {
    pub fn new(value: impl Into<String>) -> Self {
        Self(value.into())
    }

    pub fn as_str(&self) -> &str {
        &self.0
    }
}

impl AsRef<str> for FocusTarget {
    fn as_ref(&self) -> &str {
        self.as_str()
    }
}

/// Focus order metadata.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Ord, PartialOrd)]
pub struct FocusOrder(usize);

impl FocusOrder {
    pub fn new(value: usize) -> Self {
        Self(value)
    }

    pub fn value(self) -> usize {
        self.0
    }
}

/// Focus visibility preference.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Ord, PartialOrd)]
pub enum FocusVisibility {
    Auto,
    Visible,
    Hidden,
}

/// Platform-neutral tab index metadata.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Ord, PartialOrd)]
pub struct TabIndex(i16);

impl TabIndex {
    pub fn new(value: i16) -> Self {
        Self(value)
    }

    pub fn value(self) -> i16 {
        self.0
    }

    pub fn is_focusable(self) -> bool {
        self.0 >= 0
    }
}

/// A named collection of focus targets.
#[derive(Debug, Clone, Default, PartialEq, Eq)]
pub struct FocusScope {
    targets: Vec<FocusTarget>,
}

impl FocusScope {
    pub fn new(targets: Vec<FocusTarget>) -> Self {
        Self { targets }
    }

    pub fn targets(&self) -> &[FocusTarget] {
        &self.targets
    }

    pub fn contains(&self, target: &FocusTarget) -> bool {
        self.targets.contains(target)
    }
}

#[cfg(test)]
mod tests {
    use super::{FocusOrder, FocusScope, FocusState, FocusTarget, FocusVisibility, TabIndex};

    #[test]
    fn checks_focus_state_and_index_helpers() {
        assert!(FocusState::FocusVisible.has_focus());
        assert!(!FocusState::Disabled.has_focus());
        assert!(TabIndex::new(0).is_focusable());
        assert!(!TabIndex::new(-1).is_focusable());
        assert_eq!(FocusOrder::new(2).value(), 2);
        assert_eq!(FocusVisibility::Visible, FocusVisibility::Visible);
    }

    #[test]
    fn creates_focus_scopes() {
        let submit = FocusTarget::new("submit");
        let cancel = FocusTarget::new("cancel");
        let scope = FocusScope::new(vec![submit.clone()]);

        assert!(scope.contains(&submit));
        assert!(!scope.contains(&cancel));
        assert_eq!(scope.targets().len(), 1);
    }
}