use teksilo_canvas::{Rect, Size, SizeProposal};
use teksilo_core::accessibility::AccessNodeBuilder;
use teksilo_core::accesskit::{Live, Role};
use teksilo_core::build_context::BuildContext;
use teksilo_core::signal::Signal;
use teksilo_core::widget::{LayoutContext, Widget, WidgetPlacement};
use teksilo_core::widget_id::WidgetId;
use teksilo_i18n::lit;
use teksilo_tokens::{TextRole, TextStyleRole};
use super::TextWidget;
use super::text_input_field::ValidationFeedback;
pub struct ValidationStrip {
feedback: Signal<ValidationFeedback>,
root_id: Option<WidgetId>,
}
impl std::fmt::Debug for ValidationStrip {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("ValidationStrip").finish()
}
}
impl ValidationStrip {
pub fn new(feedback: Signal<ValidationFeedback>) -> Self {
Self {
feedback,
root_id: None,
}
}
}
impl Widget for ValidationStrip {
fn build(&mut self, ctx: &mut BuildContext) -> Vec<WidgetId> {
let locale_signal = ctx.locale_signal();
let text_signal = self.feedback.zip(&locale_signal).map(|(fb, _)| match fb {
ValidationFeedback::Invalid { message }
| ValidationFeedback::Corrected { message, .. } => message.resolve_now(),
_ => String::new(),
});
let color_signal: Signal<TextRole> = self.feedback.map(|fb| match fb {
ValidationFeedback::Invalid { .. } => TextRole::Error,
_ => TextRole::Secondary,
});
let label = TextWidget::new(lit!(""))
.style(TextStyleRole::Small)
.text(text_signal)
.color(teksilo_core::color_prop::ColorProp::DynamicTextRole(
color_signal,
))
.single_line()
.a11y_hidden();
let label_id = ctx.add(label);
self.root_id = Some(label_id);
let self_id = ctx.self_id();
self.feedback.bind_to(
self_id,
ctx.binding_registry(),
teksilo_core::binding::BindingLevel::AccessibilityOnly,
);
vec![label_id]
}
fn layout_response(
&self,
proposal: SizeProposal,
ctx: &LayoutContext,
) -> teksilo_core::widget::LayoutResponse {
if !matches!(
self.feedback.get(),
ValidationFeedback::Invalid { .. } | ValidationFeedback::Corrected { .. }
) {
return Size::ZERO.into();
}
match self.root_id {
Some(id) => ctx.child_size(id, proposal).unwrap_or(Size::ZERO),
None => Size::ZERO,
}
.into()
}
fn place_children(
&self,
bounds: Rect,
_proposal: SizeProposal,
children: &mut [WidgetPlacement],
_ctx: &LayoutContext,
) {
for child in children.iter_mut() {
child.origin = bounds.origin();
child.size = bounds.size();
}
}
fn children(&self) -> Vec<WidgetId> {
self.root_id.into_iter().collect()
}
fn accessibility(&self, builder: &mut AccessNodeBuilder) {
builder.set_role(Role::Status);
let fb = self.feedback.get();
match &fb {
ValidationFeedback::Invalid { message } => {
builder.set_name(message.clone());
builder.set_live(Live::Assertive);
}
ValidationFeedback::Corrected { message, .. } => {
builder.set_name(message.clone());
builder.set_live(Live::Polite);
}
_ => {
builder.set_live(Live::Off);
}
}
}
}