use std::{
any::Any,
cell::{Cell, RefCell},
fmt,
rc::Rc,
};
use dioxus::prelude::{Props, dioxus_elements, rsx};
use dioxus_core::{
Attribute, AttributeValue, Callback, Element, current_scope_id, has_context, provide_context,
try_consume_context, use_hook,
};
use dioxus_hooks::{use_effect, use_reactive, use_signal};
use dioxus_signals::{ReadSignal, ReadableExt, Signal, WritableExt};
pub mod testing;
#[allow(
clippy::struct_excessive_bools,
reason = "these independent presentation flags have no invalid combinations"
)]
#[derive(Clone, Debug, Default, PartialEq, Eq)]
pub struct FieldMetaValues {
pub id: Option<Rc<str>>,
pub name: Option<Rc<str>>,
pub required: bool,
pub disabled: bool,
pub invalid: Option<bool>,
pub errors: Vec<Rc<str>>,
pub touched: bool,
pub dirty: bool,
}
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
pub struct FieldStateOverrides {
pub invalid: Option<bool>,
pub disabled: Option<bool>,
pub required: Option<bool>,
}
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
#[non_exhaustive]
pub enum AttributeSurface {
#[default]
Native,
Aria,
Omit,
}
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
#[non_exhaustive]
pub enum ValiditySurface {
#[default]
Aria,
Omit,
}
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
#[non_exhaustive]
pub enum NameSurface {
#[default]
Native,
Omit,
}
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
pub struct FieldSurface {
pub required: AttributeSurface,
pub disabled: AttributeSurface,
pub validity: ValiditySurface,
pub name: NameSurface,
}
impl FieldSurface {
pub const NATIVE: Self = Self {
required: AttributeSurface::Native,
disabled: AttributeSurface::Native,
validity: ValiditySurface::Aria,
name: NameSurface::Native,
};
pub const BUTTON_WIDGET: Self = Self {
required: AttributeSurface::Aria,
disabled: AttributeSurface::Native,
validity: ValiditySurface::Aria,
name: NameSurface::Native,
};
pub const ARIA_WIDGET: Self = Self {
required: AttributeSurface::Aria,
disabled: AttributeSurface::Aria,
validity: ValiditySurface::Aria,
name: NameSurface::Omit,
};
}
#[derive(Clone, Debug, Default, PartialEq)]
pub struct FieldControlOptions {
state: FieldStateOverrides,
id: Option<Rc<str>>,
name: Option<Rc<str>>,
surface: FieldSurface,
}
impl FieldControlOptions {
#[must_use]
pub fn new() -> Self {
Self::default()
}
#[must_use]
pub fn state(mut self, state: FieldStateOverrides) -> Self {
self.state = state;
self
}
#[must_use]
pub fn invalid(mut self, invalid: Option<bool>) -> Self {
self.state.invalid = invalid;
self
}
#[must_use]
pub fn disabled(mut self, disabled: Option<bool>) -> Self {
self.state.disabled = disabled;
self
}
#[must_use]
pub fn required(mut self, required: Option<bool>) -> Self {
self.state.required = required;
self
}
#[must_use]
pub fn id(mut self, id: Option<Rc<str>>) -> Self {
self.id = id;
self
}
#[must_use]
pub fn name(mut self, name: Option<Rc<str>>) -> Self {
self.name = name;
self
}
#[must_use]
pub fn surface(mut self, surface: FieldSurface) -> Self {
self.surface = surface;
self
}
}
#[derive(Clone, Copy, PartialEq)]
pub struct FieldMeta {
id: Signal<Option<Rc<str>>>,
fallback_id: Signal<Rc<str>>,
name: Signal<Option<Rc<str>>>,
required: Signal<bool>,
disabled: Signal<bool>,
invalid: Signal<Option<bool>>,
errors: Signal<Vec<Rc<str>>>,
touched: Signal<bool>,
dirty: Signal<bool>,
registered_ids: Signal<RegisteredIds>,
}
impl fmt::Debug for FieldMeta {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let id = self
.id
.peek()
.clone()
.unwrap_or_else(|| self.fallback_id.peek().clone());
f.debug_struct("FieldMeta")
.field("id", &id)
.field("name", &*self.name.peek())
.field("required", &*self.required.peek())
.field("disabled", &*self.disabled.peek())
.field("invalid", &*self.invalid.peek())
.field("errors", &*self.errors.peek())
.field("touched", &*self.touched.peek())
.field("dirty", &*self.dirty.peek())
.finish_non_exhaustive()
}
}
impl FieldMeta {
pub fn id(&self) -> Rc<str> {
(self.id)().unwrap_or_else(|| (self.fallback_id)())
}
pub fn set_id(&mut self, id: Option<Rc<str>>) {
self.id.set(id);
}
pub fn name(&self) -> Option<Rc<str>> {
(self.name)()
}
pub fn set_name(&mut self, name: Option<Rc<str>>) {
self.name.set(name);
}
pub fn required(&self) -> bool {
(self.required)()
}
pub fn set_required(&mut self, required: bool) {
self.required.set(required);
}
pub fn disabled(&self) -> bool {
(self.disabled)()
}
pub fn set_disabled(&mut self, disabled: bool) {
self.disabled.set(disabled);
}
pub fn invalid(&self) -> bool {
(self.invalid)().unwrap_or_else(|| !(self.errors)().is_empty())
}
pub fn set_invalid(&mut self, invalid: Option<bool>) {
self.invalid.set(invalid);
}
pub fn errors(&self) -> Vec<Rc<str>> {
(self.errors)()
}
pub fn set_errors(&mut self, errors: Vec<Rc<str>>) {
self.errors.set(errors);
}
pub fn touched(&self) -> bool {
(self.touched)()
}
pub fn set_touched(&mut self, touched: bool) {
self.touched.set(touched);
}
pub fn dirty(&self) -> bool {
(self.dirty)()
}
pub fn set_dirty(&mut self, dirty: bool) {
self.dirty.set(dirty);
}
#[must_use]
pub fn register_label_id(&mut self, id: Rc<str>) -> FieldMetaIdRegistration {
self.register_id(RegisteredIdKind::Label, id)
}
#[must_use]
pub fn register_description_id(&mut self, id: Rc<str>) -> FieldMetaIdRegistration {
self.register_id(RegisteredIdKind::Description, id)
}
#[must_use]
pub fn register_error_id(&mut self, id: Rc<str>) -> FieldMetaIdRegistration {
self.register_id(RegisteredIdKind::Error, id)
}
pub fn attributes(&self) -> Vec<Attribute> {
self.attributes_for(&FieldControlOptions::default())
}
pub fn attributes_for(&self, options: &FieldControlOptions) -> Vec<Attribute> {
let required = options.state.required.unwrap_or_else(|| self.required());
let disabled = options.state.disabled.unwrap_or_else(|| self.disabled());
let invalid = options.state.invalid.unwrap_or_else(|| self.invalid());
let id = options.id.clone().unwrap_or_else(|| self.id());
let name = options.name.clone().or_else(|| self.name());
let registered_ids = (self.registered_ids)();
let error_ids = registered_ids.ids(RegisteredIdKind::Error);
let mut described_by = registered_ids.ids(RegisteredIdKind::Description);
if invalid {
described_by.extend(error_ids.iter().cloned());
}
let mut attributes = Vec::new();
attributes.push(Attribute::new("id", id.to_string(), None, false));
if options.surface.name == NameSurface::Native {
push_optional_text(&mut attributes, "name", name);
}
push_surface_state(
&mut attributes,
options.surface.required,
("required", "aria-required"),
required,
);
push_surface_state(
&mut attributes,
options.surface.disabled,
("disabled", "aria-disabled"),
disabled,
);
if options.surface.validity == ValiditySurface::Aria {
attributes.push(Attribute::new(
"aria-invalid",
invalid.to_string(),
None,
false,
));
if invalid {
push_optional_text(
&mut attributes,
"aria-errormessage",
error_ids.first().cloned(),
);
}
}
push_optional_text(
&mut attributes,
"aria-labelledby",
join_ids(®istered_ids.ids(RegisteredIdKind::Label)),
);
push_optional_text(&mut attributes, "aria-describedby", join_ids(&described_by));
push_state(&mut attributes, "data-required", required);
push_state(&mut attributes, "data-disabled", disabled);
push_state(&mut attributes, "data-invalid", invalid);
push_state(&mut attributes, "data-touched", self.touched());
push_state(&mut attributes, "data-dirty", self.dirty());
normalize_attributes(&mut attributes);
attributes
}
fn register_id(&mut self, kind: RegisteredIdKind, id: Rc<str>) -> FieldMetaIdRegistration {
let token = self.registered_ids.with_mut(|ids| ids.insert(kind, id));
FieldMetaIdRegistration {
registered_ids: self.registered_ids,
token,
}
}
pub fn set_values(&mut self, values: FieldMetaValues) {
if *self.id.peek() != values.id {
self.id.set(values.id);
}
if *self.name.peek() != values.name {
self.name.set(values.name);
}
if *self.required.peek() != values.required {
self.required.set(values.required);
}
if *self.disabled.peek() != values.disabled {
self.disabled.set(values.disabled);
}
if *self.invalid.peek() != values.invalid {
self.invalid.set(values.invalid);
}
if *self.errors.peek() != values.errors {
self.errors.set(values.errors);
}
if *self.touched.peek() != values.touched {
self.touched.set(values.touched);
}
if *self.dirty.peek() != values.dirty {
self.dirty.set(values.dirty);
}
}
}
pub fn use_field_meta_state(initial: FieldMetaValues) -> FieldMeta {
let FieldMetaValues {
id,
name,
required,
disabled,
invalid,
errors,
touched,
dirty,
} = initial;
FieldMeta {
id: use_signal(|| id),
fallback_id: use_signal(|| generated_id("field")),
name: use_signal(|| name),
required: use_signal(|| required),
disabled: use_signal(|| disabled),
invalid: use_signal(|| invalid),
errors: use_signal(|| errors),
touched: use_signal(|| touched),
dirty: use_signal(|| dirty),
registered_ids: use_signal(RegisteredIds::default),
}
}
fn use_synced_field_meta_state(values: &FieldMetaValues) -> FieldMeta {
let meta = use_field_meta_state(values.clone());
use_effect(use_reactive(values, move |values| {
let mut meta = meta;
meta.set_values(values);
}));
meta
}
pub struct FieldMetaIdRegistration {
registered_ids: Signal<RegisteredIds>,
token: u64,
}
impl fmt::Debug for FieldMetaIdRegistration {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("FieldMetaIdRegistration")
.field("token", &self.token)
.finish_non_exhaustive()
}
}
impl Drop for FieldMetaIdRegistration {
fn drop(&mut self) {
self.registered_ids
.with_mut(|ids| ids.entries.retain(|entry| entry.token != self.token));
}
}
#[derive(Clone, Copy, PartialEq, Eq)]
enum RegisteredIdKind {
Label,
Description,
Error,
}
#[derive(Clone, Default, PartialEq, Eq)]
struct RegisteredIds {
next_token: u64,
entries: Vec<RegisteredId>,
}
impl RegisteredIds {
fn insert(&mut self, kind: RegisteredIdKind, id: Rc<str>) -> u64 {
let token = self.next_token;
self.next_token += 1;
self.entries.push(RegisteredId { token, kind, id });
token
}
fn ids(&self, kind: RegisteredIdKind) -> Vec<Rc<str>> {
self.entries
.iter()
.filter(|entry| entry.kind == kind)
.map(|entry| Rc::clone(&entry.id))
.collect()
}
}
fn join_ids(ids: &[Rc<str>]) -> Option<Rc<str>> {
(!ids.is_empty()).then(|| {
Rc::from(
ids.iter()
.map(AsRef::as_ref)
.collect::<Vec<_>>()
.join(" ")
.as_str(),
)
})
}
#[derive(Clone)]
struct GeneratedIdCounter(Rc<Cell<u64>>);
fn generated_id(prefix: &str) -> Rc<str> {
let counter = has_context::<GeneratedIdCounter>()
.unwrap_or_else(|| provide_context(GeneratedIdCounter(Rc::new(Cell::new(0)))));
let index = counter.0.get();
counter.0.set(index + 1);
Rc::from(format!("dxf-{prefix}-{}-{index}", current_scope_id().0).as_str())
}
fn use_part_id(explicit: Option<Rc<str>>, prefix: &'static str) -> Rc<str> {
let generated = use_hook(|| generated_id(prefix));
explicit.unwrap_or(generated)
}
#[derive(Clone, PartialEq, Eq)]
struct RegisteredId {
token: u64,
kind: RegisteredIdKind,
id: Rc<str>,
}
fn push_optional_text(attributes: &mut Vec<Attribute>, name: &'static str, value: Option<Rc<str>>) {
if let Some(value) = value {
attributes.push(Attribute::new(name, value.to_string(), None, false));
}
}
fn push_bool(attributes: &mut Vec<Attribute>, name: &'static str, value: bool) {
if value {
attributes.push(Attribute::new(name, true, None, false));
}
}
fn push_surface_state(
attributes: &mut Vec<Attribute>,
surface: AttributeSurface,
(native, aria): (&'static str, &'static str),
value: bool,
) {
match surface {
AttributeSurface::Native => push_bool(attributes, native, value),
AttributeSurface::Aria => push_state(attributes, aria, value),
AttributeSurface::Omit => {}
}
}
fn push_state(attributes: &mut Vec<Attribute>, name: &'static str, value: bool) {
if value {
attributes.push(Attribute::new(name, "true", None, false));
}
}
pub fn merge_attributes(groups: Vec<Vec<Attribute>>) -> Vec<Attribute> {
let mut attributes = groups.into_iter().flatten().collect::<Vec<_>>();
normalize_attributes(&mut attributes);
attributes
}
fn normalize_attributes(attributes: &mut Vec<Attribute>) {
attributes.sort_by(|left, right| {
left.name
.cmp(right.name)
.then_with(|| left.namespace.cmp(&right.namespace))
});
attributes.dedup_by(|later, earlier| {
let duplicate = later.name == earlier.name && later.namespace == earlier.namespace;
if duplicate {
if let ("class", AttributeValue::Text(kept), AttributeValue::Text(dropped)) =
(later.name, &later.value, &earlier.value)
{
let combined = format!("{dropped} {kept}");
later.value = AttributeValue::Text(combined);
}
std::mem::swap(later, earlier);
}
duplicate
});
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum ChangeOrigin {
User,
Programmatic,
}
pub struct Binding<T: 'static> {
pub read: ReadSignal<T>,
write: Callback<(T, ChangeOrigin)>,
commit: Callback<()>,
identity: BindingIdentity,
}
impl<T: 'static> Binding<T> {
pub fn new(
read: ReadSignal<T>,
write: Callback<(T, ChangeOrigin)>,
commit: Callback<()>,
) -> Self {
Self::new_with_identity(read, write, commit, (read, write, commit))
}
pub fn new_with_identity<I>(
read: ReadSignal<T>,
write: Callback<(T, ChangeOrigin)>,
commit: Callback<()>,
identity: I,
) -> Self
where
I: PartialEq + 'static,
{
Self {
read,
write,
commit,
identity: BindingIdentity::new(identity),
}
}
pub fn write(&self, value: T, origin: ChangeOrigin) {
self.write.call((value, origin));
}
pub fn commit(&self) {
self.commit.call(());
}
pub fn into_trio(self) -> BindingPropTrio<T> {
let value = self.read;
let on_commit = self.commit;
let on_change = Callback::new(move |value| self.write(value, ChangeOrigin::User));
BindingPropTrio {
value,
on_change,
on_commit,
}
}
}
impl<T: fmt::Debug + 'static> fmt::Debug for Binding<T> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("Binding")
.field("read", &*self.read.peek())
.finish_non_exhaustive()
}
}
impl<T: 'static> Clone for Binding<T> {
fn clone(&self) -> Self {
Self {
read: self.read,
write: self.write,
commit: self.commit,
identity: self.identity.clone(),
}
}
}
impl<T: 'static> PartialEq for Binding<T> {
fn eq(&self, other: &Self) -> bool {
self.identity == other.identity
}
}
impl<T: 'static> From<Signal<T>> for Binding<T> {
fn from(signal: Signal<T>) -> Self {
let read = ReadSignal::from(signal);
let mut writer = signal;
let write = Callback::new(move |(value, _origin)| writer.set(value));
let commit = Callback::new(|()| {});
Self::new_with_identity(read, write, commit, signal)
}
}
impl<T: 'static> From<(ReadSignal<T>, Callback<T>)> for Binding<T> {
fn from((read, on_change): (ReadSignal<T>, Callback<T>)) -> Self {
let write = Callback::new(move |(value, _origin)| on_change.call(value));
let commit = Callback::new(|()| {});
Self::new_with_identity(read, write, commit, (read, on_change))
}
}
impl<T: 'static> From<T> for Binding<T> {
fn from(value: T) -> Self {
Signal::new(value).into()
}
}
pub struct BindingPropTrio<T: 'static> {
pub value: ReadSignal<T>,
pub on_change: Callback<T>,
pub on_commit: Callback<()>,
}
impl<T: fmt::Debug + 'static> fmt::Debug for BindingPropTrio<T> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("BindingPropTrio")
.field("value", &*self.value.peek())
.field("on_change", &self.on_change)
.field("on_commit", &self.on_commit)
.finish()
}
}
impl<T: 'static> From<Binding<T>> for BindingPropTrio<T> {
fn from(binding: Binding<T>) -> Self {
binding.into_trio()
}
}
#[derive(Clone, Default)]
pub struct FocusRequest(Rc<RefCell<FocusRequestState>>);
impl FocusRequest {
#[must_use]
pub fn register(&self, callback: Callback<()>) -> FocusRegistration {
let mut state = self.0.borrow_mut();
let token = state.next_token;
state.next_token += 1;
state.current = Some((token, callback));
FocusRegistration {
request: self.clone(),
token,
}
}
pub fn request(&self) -> bool {
let callback = self.0.borrow().current.map(|(_, callback)| callback);
if let Some(callback) = callback {
callback.call(());
true
} else {
false
}
}
}
impl fmt::Debug for FocusRequest {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("FocusRequest")
.field("registered", &self.0.borrow().current.is_some())
.finish()
}
}
impl PartialEq for FocusRequest {
fn eq(&self, other: &Self) -> bool {
Rc::ptr_eq(&self.0, &other.0)
}
}
#[derive(Default)]
struct FocusRequestState {
next_token: u64,
current: Option<(u64, Callback<()>)>,
}
pub struct FocusRegistration {
request: FocusRequest,
token: u64,
}
impl fmt::Debug for FocusRegistration {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("FocusRegistration")
.field("request", &self.request)
.field("token", &self.token)
.finish()
}
}
impl Drop for FocusRegistration {
fn drop(&mut self) {
let mut state = self.request.0.borrow_mut();
if state.current.is_some_and(|(token, _)| token == self.token) {
state.current = None;
}
}
}
#[derive(Clone)]
pub struct FieldContext {
binding: Option<ErasedBinding>,
meta: Option<FieldMeta>,
meta_values: Option<FieldMetaValues>,
focus_request: FocusRequest,
}
impl FieldContext {
pub fn new<T: 'static>(binding: Binding<T>) -> Self {
Self {
binding: Some(ErasedBinding::new(binding)),
meta: None,
meta_values: None,
focus_request: FocusRequest::default(),
}
}
pub fn empty() -> Self {
Self {
binding: None,
meta: None,
meta_values: None,
focus_request: FocusRequest::default(),
}
}
#[must_use]
pub fn with_binding<T: 'static>(mut self, binding: Binding<T>) -> Self {
self.binding = Some(ErasedBinding::new(binding));
self
}
#[must_use]
pub fn with_meta(mut self, meta: FieldMeta) -> Self {
self.meta = Some(meta);
self.meta_values = None;
self
}
#[must_use]
pub fn with_meta_values(mut self, values: FieldMetaValues) -> Self {
self.meta = None;
self.meta_values = Some(values);
self
}
pub fn meta(&self) -> Option<FieldMeta> {
self.meta
}
pub fn focus_request(&self) -> FocusRequest {
self.focus_request.clone()
}
pub fn resolve<T: 'static>(&self) -> Binding<T> {
let erased = self
.binding
.as_ref()
.unwrap_or_else(|| panic!("Field Context contains no value binding"));
erased
.binding
.downcast_ref::<Binding<T>>()
.unwrap_or_else(|| {
panic!(
"Field Context contains a binding for {}, but a binding for {} was requested",
erased.value_type_name,
std::any::type_name::<T>()
)
})
.clone()
}
fn try_resolve<T: 'static>(&self) -> Option<Binding<T>> {
self.binding.as_ref().map(|_| self.resolve())
}
fn with_focus_request(mut self, focus_request: FocusRequest) -> Self {
self.focus_request = focus_request;
self
}
}
impl fmt::Debug for FieldContext {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("FieldContext")
.field(
"value_type_name",
&self.binding.as_ref().map(|binding| binding.value_type_name),
)
.field("meta", &self.meta)
.field("meta_values", &self.meta_values)
.field("focus_request", &self.focus_request)
.finish_non_exhaustive()
}
}
impl PartialEq for FieldContext {
fn eq(&self, other: &Self) -> bool {
self.binding == other.binding
&& self.meta == other.meta
&& self.meta_values == other.meta_values
}
}
impl<T: 'static> From<Binding<T>> for FieldContext {
fn from(binding: Binding<T>) -> Self {
Self::new(binding)
}
}
impl<T: 'static> From<Signal<T>> for FieldContext {
fn from(signal: Signal<T>) -> Self {
Self::new(Binding::<T>::from(signal))
}
}
#[derive(Clone)]
struct ErasedBinding {
binding: Rc<dyn Any>,
value_type_name: &'static str,
eq: fn(&dyn Any, &dyn Any) -> bool,
}
impl ErasedBinding {
fn new<T: 'static>(binding: Binding<T>) -> Self {
Self {
binding: Rc::new(binding),
value_type_name: std::any::type_name::<T>(),
eq: |left, right| match (
left.downcast_ref::<Binding<T>>(),
right.downcast_ref::<Binding<T>>(),
) {
(Some(left), Some(right)) => left == right,
_ => false,
},
}
}
}
impl PartialEq for ErasedBinding {
fn eq(&self, other: &Self) -> bool {
(self.eq)(&*self.binding, &*other.binding)
}
}
pub fn provide_field_context<T: 'static>(binding: Binding<T>) -> FieldContext {
let mut context = FieldContext::new(binding);
if let Some(existing) = has_context::<FieldContext>() {
context = context.with_focus_request(existing.focus_request());
}
provide_context(context)
}
pub fn use_binding<T: 'static>(explicit: Option<Binding<T>>, default: T) -> Binding<T> {
let internal = use_signal(|| default);
if let Some(binding) = explicit {
return binding;
}
if let Some(binding) =
try_consume_context::<FieldContext>().and_then(|context| context.try_resolve())
{
return binding;
}
internal.into()
}
pub fn use_field_meta(explicit: Option<FieldMeta>) -> FieldMeta {
use_resolved_field_meta(explicit.as_ref()).0
}
#[derive(Clone, Copy, PartialEq, Eq)]
enum FieldMetaSource {
Shared,
Standalone,
}
fn use_resolved_field_meta(explicit: Option<&FieldMeta>) -> (FieldMeta, FieldMetaSource) {
let internal = use_field_meta_state(FieldMetaValues::default());
explicit
.copied()
.or_else(|| try_consume_context::<FieldContext>().and_then(|context| context.meta()))
.map_or((internal, FieldMetaSource::Standalone), |meta| {
(meta, FieldMetaSource::Shared)
})
}
pub fn use_focus_request() -> FocusRequest {
let internal = use_hook(FocusRequest::default);
try_consume_context::<FieldContext>().map_or(internal, |context| context.focus_request())
}
pub fn use_focus_registration(callback: Callback<()>) -> FocusRequest {
let request = use_focus_request();
let active = use_hook(|| Rc::new(RefCell::new(None::<ActiveFocusRegistration>)));
let should_replace = active
.borrow()
.as_ref()
.is_none_or(|active| active.request != request || active.callback != callback);
if should_replace {
let registration = request.register(callback);
active.borrow_mut().replace(ActiveFocusRegistration {
request: request.clone(),
callback,
_registration: registration,
});
}
request
}
struct ActiveFocusRegistration {
request: FocusRequest,
callback: Callback<()>,
_registration: FocusRegistration,
}
#[derive(Clone, Debug, Props, PartialEq)]
pub struct FieldProps {
#[props(into)]
pub context: FieldContext,
#[props(extends = GlobalAttributes)]
pub attributes: Vec<Attribute>,
pub children: Element,
}
#[allow(non_snake_case)]
#[allow(
clippy::missing_errors_doc,
reason = "Dioxus Element uses Result as its renderer protocol"
)]
pub fn Field(props: FieldProps) -> Element {
let has_meta_values = props.context.meta_values.is_some();
let meta_values = props.context.meta_values.clone().unwrap_or_default();
let synced_meta = use_synced_field_meta_state(&meta_values);
let mut context = props.context;
if has_meta_values {
context = context.with_meta(synced_meta);
}
let focus_request = use_hook(|| context.focus_request());
provide_context(context.with_focus_request(focus_request));
let mut attributes = props.attributes;
normalize_attributes(&mut attributes);
rsx! {
div { ..attributes, {props.children} }
}
}
#[derive(Clone, Debug, Props, PartialEq)]
pub struct LabelProps {
#[props(default)]
pub id: Option<Rc<str>>,
#[props(default)]
pub meta: Option<FieldMeta>,
#[props(default)]
pub invalid: Option<bool>,
#[props(default)]
pub disabled: Option<bool>,
#[props(default)]
pub required: Option<bool>,
#[props(extends = GlobalAttributes)]
pub attributes: Vec<Attribute>,
pub children: Element,
}
#[allow(non_snake_case)]
#[allow(
clippy::missing_errors_doc,
reason = "Dioxus Element uses Result as its renderer protocol"
)]
pub fn Label(props: LabelProps) -> Element {
let (meta, source) = use_resolved_field_meta(props.meta.as_ref());
let id = use_part_id(props.id, "label");
use_field_meta_id_registration(&meta, RegisteredIdKind::Label, Rc::clone(&id));
let control_id = (source == FieldMetaSource::Shared).then(|| meta.id().to_string());
let attributes = part_attributes(
&meta,
FieldStateOverrides {
invalid: props.invalid,
disabled: props.disabled,
required: props.required,
},
props.attributes,
);
rsx! {
label { id: id.to_string(), r#for: control_id, ..attributes, {props.children} }
}
}
#[derive(Clone, Debug, Props, PartialEq)]
pub struct FieldDescriptionProps {
#[props(into)]
pub id: Rc<str>,
#[props(default)]
pub meta: Option<FieldMeta>,
#[props(default)]
pub invalid: Option<bool>,
#[props(default)]
pub disabled: Option<bool>,
#[props(default)]
pub required: Option<bool>,
#[props(extends = GlobalAttributes)]
pub attributes: Vec<Attribute>,
pub children: Element,
}
#[allow(non_snake_case)]
#[allow(
clippy::missing_errors_doc,
reason = "Dioxus Element uses Result as its renderer protocol"
)]
pub fn FieldDescription(props: FieldDescriptionProps) -> Element {
let meta = use_field_meta(props.meta);
use_field_meta_id_registration(&meta, RegisteredIdKind::Description, props.id.clone());
let id = props.id.to_string();
let attributes = part_attributes(
&meta,
FieldStateOverrides {
invalid: props.invalid,
disabled: props.disabled,
required: props.required,
},
props.attributes,
);
rsx! {
div { id: id, ..attributes, {props.children} }
}
}
#[derive(Clone, Debug, Props, PartialEq)]
pub struct FieldErrorProps {
#[props(into)]
pub id: Rc<str>,
#[props(default)]
pub meta: Option<FieldMeta>,
#[props(default)]
pub invalid: Option<bool>,
#[props(default)]
pub disabled: Option<bool>,
#[props(default)]
pub required: Option<bool>,
#[props(extends = GlobalAttributes)]
pub attributes: Vec<Attribute>,
}
#[allow(non_snake_case)]
#[allow(
clippy::missing_errors_doc,
reason = "Dioxus Element uses Result as its renderer protocol"
)]
pub fn FieldError(props: FieldErrorProps) -> Element {
let meta = use_field_meta(props.meta);
use_field_meta_id_registration(&meta, RegisteredIdKind::Error, props.id.clone());
let invalid = props.invalid.unwrap_or_else(|| meta.invalid());
let id = props.id.to_string();
let errors = if invalid { meta.errors() } else { Vec::new() };
let attributes = part_attributes(
&meta,
FieldStateOverrides {
invalid: props.invalid,
disabled: props.disabled,
required: props.required,
},
props.attributes,
);
rsx! {
div {
id: id,
aria_live: "polite",
..attributes,
for error in errors {
div { "{error}" }
}
}
}
}
fn part_attributes(
meta: &FieldMeta,
overrides: FieldStateOverrides,
forwarded: Vec<Attribute>,
) -> Vec<Attribute> {
let required = overrides.required.unwrap_or_else(|| meta.required());
let disabled = overrides.disabled.unwrap_or_else(|| meta.disabled());
let invalid = overrides.invalid.unwrap_or_else(|| meta.invalid());
let mut attributes = Vec::new();
push_state(&mut attributes, "data-required", required);
push_state(&mut attributes, "data-disabled", disabled);
push_state(&mut attributes, "data-invalid", invalid);
push_state(&mut attributes, "data-touched", meta.touched());
push_state(&mut attributes, "data-dirty", meta.dirty());
attributes.extend(forwarded);
normalize_attributes(&mut attributes);
attributes
}
fn use_field_meta_id_registration(meta: &FieldMeta, kind: RegisteredIdKind, id: Rc<str>) {
let active = use_hook(|| Rc::new(RefCell::new(None::<ActiveFieldMetaIdRegistration>)));
let should_replace = active
.borrow()
.as_ref()
.is_none_or(|active| active.meta != *meta || active.kind != kind || active.id != id);
if should_replace {
let mut writable_meta = *meta;
let registration = writable_meta.register_id(kind, id.clone());
active.borrow_mut().replace(ActiveFieldMetaIdRegistration {
meta: *meta,
kind,
id,
_registration: registration,
});
}
}
struct ActiveFieldMetaIdRegistration {
meta: FieldMeta,
kind: RegisteredIdKind,
id: Rc<str>,
_registration: FieldMetaIdRegistration,
}
#[derive(Clone)]
struct BindingIdentity(Rc<dyn ComparableIdentity>);
impl BindingIdentity {
fn new<I: PartialEq + 'static>(identity: I) -> Self {
Self(Rc::new(identity))
}
}
impl PartialEq for BindingIdentity {
fn eq(&self, other: &Self) -> bool {
self.0.equals(other.0.as_ref())
}
}
trait ComparableIdentity: Any {
fn equals(&self, other: &dyn ComparableIdentity) -> bool;
}
impl<I: PartialEq + 'static> ComparableIdentity for I {
fn equals(&self, other: &dyn ComparableIdentity) -> bool {
let other = other as &dyn Any;
other.downcast_ref::<I>().is_some_and(|other| self == other)
}
}