1use std::{any::Any, cell::RefCell, fmt, rc::Rc};
29
30use dioxus::prelude::{Props, dioxus_elements, rsx};
31use dioxus_core::{
32 Attribute, Callback, Element, has_context, provide_context, try_consume_context, use_hook,
33};
34use dioxus_hooks::{use_effect, use_reactive, use_signal};
35use dioxus_signals::{ReadSignal, ReadableExt, Signal, WritableExt};
36
37pub mod testing;
38
39#[allow(
44 clippy::struct_excessive_bools,
45 reason = "these independent presentation flags have no invalid combinations"
46)]
47#[derive(Clone, Debug, Default, PartialEq, Eq)]
48pub struct FieldMetaValues {
49 pub id: Option<Rc<str>>,
51 pub name: Option<Rc<str>>,
53 pub required: bool,
55 pub disabled: bool,
57 pub invalid: Option<bool>,
59 pub errors: Vec<Rc<str>>,
61 pub touched: bool,
63 pub dirty: bool,
65}
66
67#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
69pub struct FieldMetaOverrides {
70 pub invalid: Option<bool>,
72 pub disabled: Option<bool>,
74}
75
76#[derive(Clone, Copy, PartialEq)]
81pub struct FieldMeta {
82 id: Signal<Option<Rc<str>>>,
83 name: Signal<Option<Rc<str>>>,
84 required: Signal<bool>,
85 disabled: Signal<bool>,
86 invalid: Signal<Option<bool>>,
87 errors: Signal<Vec<Rc<str>>>,
88 touched: Signal<bool>,
89 dirty: Signal<bool>,
90 registered_ids: Signal<RegisteredIds>,
91}
92
93impl fmt::Debug for FieldMeta {
94 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
95 f.debug_struct("FieldMeta")
96 .field("id", &*self.id.peek())
97 .field("name", &*self.name.peek())
98 .field("required", &*self.required.peek())
99 .field("disabled", &*self.disabled.peek())
100 .field("invalid", &*self.invalid.peek())
101 .field("errors", &*self.errors.peek())
102 .field("touched", &*self.touched.peek())
103 .field("dirty", &*self.dirty.peek())
104 .finish_non_exhaustive()
105 }
106}
107
108impl FieldMeta {
109 pub fn id(&self) -> Option<Rc<str>> {
111 (self.id)()
112 }
113
114 pub fn set_id(&mut self, id: Option<Rc<str>>) {
116 self.id.set(id);
117 }
118
119 pub fn name(&self) -> Option<Rc<str>> {
121 (self.name)()
122 }
123
124 pub fn set_name(&mut self, name: Option<Rc<str>>) {
126 self.name.set(name);
127 }
128
129 pub fn required(&self) -> bool {
131 (self.required)()
132 }
133
134 pub fn set_required(&mut self, required: bool) {
136 self.required.set(required);
137 }
138
139 pub fn disabled(&self) -> bool {
141 (self.disabled)()
142 }
143
144 pub fn set_disabled(&mut self, disabled: bool) {
146 self.disabled.set(disabled);
147 }
148
149 pub fn invalid(&self) -> bool {
153 (self.invalid)().unwrap_or_else(|| !(self.errors)().is_empty())
154 }
155
156 pub fn set_invalid(&mut self, invalid: Option<bool>) {
158 self.invalid.set(invalid);
159 }
160
161 pub fn errors(&self) -> Vec<Rc<str>> {
163 (self.errors)()
164 }
165
166 pub fn set_errors(&mut self, errors: Vec<Rc<str>>) {
168 self.errors.set(errors);
169 }
170
171 pub fn touched(&self) -> bool {
173 (self.touched)()
174 }
175
176 pub fn set_touched(&mut self, touched: bool) {
178 self.touched.set(touched);
179 }
180
181 pub fn dirty(&self) -> bool {
183 (self.dirty)()
184 }
185
186 pub fn set_dirty(&mut self, dirty: bool) {
188 self.dirty.set(dirty);
189 }
190
191 #[must_use]
193 pub fn register_description_id(&mut self, id: Rc<str>) -> FieldMetaIdRegistration {
194 self.register_id(RegisteredIdKind::Description, id)
195 }
196
197 #[must_use]
199 pub fn register_error_id(&mut self, id: Rc<str>) -> FieldMetaIdRegistration {
200 self.register_id(RegisteredIdKind::Error, id)
201 }
202
203 pub fn attributes(&self) -> Vec<Attribute> {
205 self.attributes_with(FieldMetaOverrides::default())
206 }
207
208 pub fn attributes_with(&self, overrides: FieldMetaOverrides) -> Vec<Attribute> {
210 let invalid = overrides.invalid.unwrap_or_else(|| self.invalid());
211 let disabled = overrides.disabled.unwrap_or_else(|| self.disabled());
212 let registered_ids = (self.registered_ids)();
213 let description_ids = registered_ids.joined(RegisteredIdKind::Description);
214 let error_ids = registered_ids.joined(RegisteredIdKind::Error);
215 let mut attributes = Vec::new();
216
217 push_optional_text(&mut attributes, "id", self.id());
218 push_optional_text(&mut attributes, "name", self.name());
219 push_bool(&mut attributes, "required", self.required());
220 push_bool(&mut attributes, "disabled", disabled);
221 attributes.push(Attribute::new(
222 "aria-invalid",
223 invalid.to_string(),
224 None,
225 false,
226 ));
227 push_optional_text(&mut attributes, "aria-describedby", description_ids);
228 if invalid {
229 push_optional_text(&mut attributes, "aria-errormessage", error_ids);
230 }
231 push_data_state(&mut attributes, "data-required", self.required());
232 push_data_state(&mut attributes, "data-disabled", disabled);
233 push_data_state(&mut attributes, "data-invalid", invalid);
234 push_data_state(&mut attributes, "data-touched", self.touched());
235 push_data_state(&mut attributes, "data-dirty", self.dirty());
236
237 attributes
238 }
239
240 fn register_id(&mut self, kind: RegisteredIdKind, id: Rc<str>) -> FieldMetaIdRegistration {
241 let token = self.registered_ids.with_mut(|ids| ids.insert(kind, id));
242
243 FieldMetaIdRegistration {
244 registered_ids: self.registered_ids,
245 token,
246 }
247 }
248
249 pub fn set_values(&mut self, values: FieldMetaValues) {
251 if *self.id.peek() != values.id {
252 self.id.set(values.id);
253 }
254 if *self.name.peek() != values.name {
255 self.name.set(values.name);
256 }
257 if *self.required.peek() != values.required {
258 self.required.set(values.required);
259 }
260 if *self.disabled.peek() != values.disabled {
261 self.disabled.set(values.disabled);
262 }
263 if *self.invalid.peek() != values.invalid {
264 self.invalid.set(values.invalid);
265 }
266 if *self.errors.peek() != values.errors {
267 self.errors.set(values.errors);
268 }
269 if *self.touched.peek() != values.touched {
270 self.touched.set(values.touched);
271 }
272 if *self.dirty.peek() != values.dirty {
273 self.dirty.set(values.dirty);
274 }
275 }
276}
277
278pub fn use_field_meta_state(initial: FieldMetaValues) -> FieldMeta {
280 let FieldMetaValues {
281 id,
282 name,
283 required,
284 disabled,
285 invalid,
286 errors,
287 touched,
288 dirty,
289 } = initial;
290
291 FieldMeta {
292 id: use_signal(|| id),
293 name: use_signal(|| name),
294 required: use_signal(|| required),
295 disabled: use_signal(|| disabled),
296 invalid: use_signal(|| invalid),
297 errors: use_signal(|| errors),
298 touched: use_signal(|| touched),
299 dirty: use_signal(|| dirty),
300 registered_ids: use_signal(RegisteredIds::default),
301 }
302}
303
304fn use_synced_field_meta_state(values: &FieldMetaValues) -> FieldMeta {
305 let meta = use_field_meta_state(values.clone());
306 use_effect(use_reactive(values, move |values| {
307 let mut meta = meta;
308 meta.set_values(values);
309 }));
310
311 meta
312}
313
314pub struct FieldMetaIdRegistration {
316 registered_ids: Signal<RegisteredIds>,
317 token: u64,
318}
319
320impl fmt::Debug for FieldMetaIdRegistration {
321 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
322 f.debug_struct("FieldMetaIdRegistration")
323 .field("token", &self.token)
324 .finish_non_exhaustive()
325 }
326}
327
328impl Drop for FieldMetaIdRegistration {
329 fn drop(&mut self) {
330 self.registered_ids
331 .with_mut(|ids| ids.entries.retain(|entry| entry.token != self.token));
332 }
333}
334
335#[derive(Clone, Copy, PartialEq, Eq)]
336enum RegisteredIdKind {
337 Description,
338 Error,
339}
340
341#[derive(Clone, Default, PartialEq, Eq)]
342struct RegisteredIds {
343 next_token: u64,
344 entries: Vec<RegisteredId>,
345}
346
347impl RegisteredIds {
348 fn insert(&mut self, kind: RegisteredIdKind, id: Rc<str>) -> u64 {
349 let token = self.next_token;
350 self.next_token += 1;
351 self.entries.push(RegisteredId { token, kind, id });
352
353 token
354 }
355
356 fn joined(&self, kind: RegisteredIdKind) -> Option<Rc<str>> {
357 let ids = self
358 .entries
359 .iter()
360 .filter(|entry| entry.kind == kind)
361 .map(|entry| entry.id.as_ref())
362 .collect::<Vec<_>>();
363
364 (!ids.is_empty()).then(|| Rc::from(ids.join(" ")))
365 }
366}
367
368#[derive(Clone, PartialEq, Eq)]
369struct RegisteredId {
370 token: u64,
371 kind: RegisteredIdKind,
372 id: Rc<str>,
373}
374
375fn push_optional_text(attributes: &mut Vec<Attribute>, name: &'static str, value: Option<Rc<str>>) {
376 if let Some(value) = value {
377 attributes.push(Attribute::new(name, value.to_string(), None, false));
378 }
379}
380
381fn push_bool(attributes: &mut Vec<Attribute>, name: &'static str, value: bool) {
382 if value {
383 attributes.push(Attribute::new(name, true, None, false));
384 }
385}
386
387fn push_data_state(attributes: &mut Vec<Attribute>, name: &'static str, value: bool) {
388 if value {
389 attributes.push(Attribute::new(name, "true", None, false));
390 }
391}
392
393#[derive(Clone, Copy, Debug, PartialEq, Eq)]
395pub enum ChangeOrigin {
396 User,
398 Programmatic,
400}
401
402pub struct Binding<T: 'static> {
408 pub read: ReadSignal<T>,
410 write: Callback<(T, ChangeOrigin)>,
411 commit: Callback<()>,
412 identity: BindingIdentity,
413}
414
415impl<T: 'static> Binding<T> {
416 pub fn new(
418 read: ReadSignal<T>,
419 write: Callback<(T, ChangeOrigin)>,
420 commit: Callback<()>,
421 ) -> Self {
422 Self::new_with_identity(read, write, commit, (read, write, commit))
423 }
424
425 pub fn new_with_identity<I>(
430 read: ReadSignal<T>,
431 write: Callback<(T, ChangeOrigin)>,
432 commit: Callback<()>,
433 identity: I,
434 ) -> Self
435 where
436 I: PartialEq + 'static,
437 {
438 Self {
439 read,
440 write,
441 commit,
442 identity: BindingIdentity::new(identity),
443 }
444 }
445
446 pub fn write(&self, value: T, origin: ChangeOrigin) {
448 self.write.call((value, origin));
449 }
450
451 pub fn commit(&self) {
453 self.commit.call(());
454 }
455
456 pub fn into_trio(self) -> BindingPropTrio<T> {
460 let value = self.read;
461 let on_commit = self.commit;
462 let on_change = Callback::new(move |value| self.write(value, ChangeOrigin::User));
463
464 BindingPropTrio {
465 value,
466 on_change,
467 on_commit,
468 }
469 }
470}
471
472impl<T: fmt::Debug + 'static> fmt::Debug for Binding<T> {
473 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
474 f.debug_struct("Binding")
475 .field("read", &*self.read.peek())
476 .finish_non_exhaustive()
477 }
478}
479
480impl<T: 'static> Clone for Binding<T> {
481 fn clone(&self) -> Self {
482 Self {
483 read: self.read,
484 write: self.write,
485 commit: self.commit,
486 identity: self.identity.clone(),
487 }
488 }
489}
490
491impl<T: 'static> PartialEq for Binding<T> {
492 fn eq(&self, other: &Self) -> bool {
493 self.identity == other.identity
494 }
495}
496
497impl<T: 'static> From<Signal<T>> for Binding<T> {
498 fn from(signal: Signal<T>) -> Self {
499 let read = ReadSignal::from(signal);
500 let mut writer = signal;
501 let write = Callback::new(move |(value, _origin)| writer.set(value));
502 let commit = Callback::new(|()| {});
503
504 Self::new_with_identity(read, write, commit, signal)
505 }
506}
507
508impl<T: 'static> From<(ReadSignal<T>, Callback<T>)> for Binding<T> {
509 fn from((read, on_change): (ReadSignal<T>, Callback<T>)) -> Self {
510 let write = Callback::new(move |(value, _origin)| on_change.call(value));
511 let commit = Callback::new(|()| {});
512
513 Self::new_with_identity(read, write, commit, (read, on_change))
514 }
515}
516
517impl<T: 'static> From<T> for Binding<T> {
518 fn from(value: T) -> Self {
519 Signal::new(value).into()
520 }
521}
522
523pub struct BindingPropTrio<T: 'static> {
528 pub value: ReadSignal<T>,
530 pub on_change: Callback<T>,
532 pub on_commit: Callback<()>,
534}
535
536impl<T: fmt::Debug + 'static> fmt::Debug for BindingPropTrio<T> {
537 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
538 f.debug_struct("BindingPropTrio")
539 .field("value", &*self.value.peek())
540 .field("on_change", &self.on_change)
541 .field("on_commit", &self.on_commit)
542 .finish()
543 }
544}
545
546impl<T: 'static> From<Binding<T>> for BindingPropTrio<T> {
547 fn from(binding: Binding<T>) -> Self {
548 binding.into_trio()
549 }
550}
551
552#[derive(Clone, Default)]
554pub struct FocusRequest(Rc<RefCell<FocusRequestState>>);
555
556impl FocusRequest {
557 #[must_use]
562 pub fn register(&self, callback: Callback<()>) -> FocusRegistration {
563 let mut state = self.0.borrow_mut();
564 let token = state.next_token;
565 state.next_token += 1;
566 state.current = Some((token, callback));
567
568 FocusRegistration {
569 request: self.clone(),
570 token,
571 }
572 }
573
574 pub fn request(&self) -> bool {
578 let callback = self.0.borrow().current.map(|(_, callback)| callback);
579
580 if let Some(callback) = callback {
581 callback.call(());
582 true
583 } else {
584 false
585 }
586 }
587}
588
589impl fmt::Debug for FocusRequest {
590 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
591 f.debug_struct("FocusRequest")
592 .field("registered", &self.0.borrow().current.is_some())
593 .finish()
594 }
595}
596
597impl PartialEq for FocusRequest {
598 fn eq(&self, other: &Self) -> bool {
599 Rc::ptr_eq(&self.0, &other.0)
600 }
601}
602
603#[derive(Default)]
604struct FocusRequestState {
605 next_token: u64,
606 current: Option<(u64, Callback<()>)>,
607}
608
609pub struct FocusRegistration {
611 request: FocusRequest,
612 token: u64,
613}
614
615impl fmt::Debug for FocusRegistration {
616 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
617 f.debug_struct("FocusRegistration")
618 .field("request", &self.request)
619 .field("token", &self.token)
620 .finish()
621 }
622}
623
624impl Drop for FocusRegistration {
625 fn drop(&mut self) {
626 let mut state = self.request.0.borrow_mut();
627
628 if state.current.is_some_and(|(token, _)| token == self.token) {
629 state.current = None;
630 }
631 }
632}
633
634#[derive(Clone)]
648pub struct FieldContext {
649 binding: Option<ErasedBinding>,
650 meta: Option<FieldMeta>,
651 meta_values: Option<FieldMetaValues>,
652 focus_request: FocusRequest,
653}
654
655impl FieldContext {
656 pub fn new<T: 'static>(binding: Binding<T>) -> Self {
658 Self {
659 binding: Some(ErasedBinding::new(binding)),
660 meta: None,
661 meta_values: None,
662 focus_request: FocusRequest::default(),
663 }
664 }
665
666 pub fn empty() -> Self {
668 Self {
669 binding: None,
670 meta: None,
671 meta_values: None,
672 focus_request: FocusRequest::default(),
673 }
674 }
675
676 #[must_use]
678 pub fn with_binding<T: 'static>(mut self, binding: Binding<T>) -> Self {
679 self.binding = Some(ErasedBinding::new(binding));
680 self
681 }
682
683 #[must_use]
685 pub fn with_meta(mut self, meta: FieldMeta) -> Self {
686 self.meta = Some(meta);
687 self.meta_values = None;
688 self
689 }
690
691 #[must_use]
693 pub fn with_meta_values(mut self, values: FieldMetaValues) -> Self {
694 self.meta = None;
695 self.meta_values = Some(values);
696 self
697 }
698
699 pub fn meta(&self) -> Option<FieldMeta> {
701 self.meta
702 }
703
704 pub fn focus_request(&self) -> FocusRequest {
709 self.focus_request.clone()
710 }
711
712 pub fn resolve<T: 'static>(&self) -> Binding<T> {
718 let erased = self
719 .binding
720 .as_ref()
721 .unwrap_or_else(|| panic!("Field Context contains no value binding"));
722
723 erased
724 .binding
725 .downcast_ref::<Binding<T>>()
726 .unwrap_or_else(|| {
727 panic!(
728 "Field Context contains a binding for {}, but a binding for {} was requested",
729 erased.value_type_name,
730 std::any::type_name::<T>()
731 )
732 })
733 .clone()
734 }
735
736 fn try_resolve<T: 'static>(&self) -> Option<Binding<T>> {
737 self.binding.as_ref().map(|_| self.resolve())
738 }
739
740 fn with_focus_request(mut self, focus_request: FocusRequest) -> Self {
741 self.focus_request = focus_request;
742 self
743 }
744}
745
746impl fmt::Debug for FieldContext {
747 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
748 f.debug_struct("FieldContext")
749 .field(
750 "value_type_name",
751 &self.binding.as_ref().map(|binding| binding.value_type_name),
752 )
753 .field("meta", &self.meta)
754 .field("meta_values", &self.meta_values)
755 .field("focus_request", &self.focus_request)
756 .finish_non_exhaustive()
757 }
758}
759
760impl PartialEq for FieldContext {
761 fn eq(&self, other: &Self) -> bool {
762 self.binding == other.binding
763 && self.meta == other.meta
764 && self.meta_values == other.meta_values
765 }
766}
767
768impl<T: 'static> From<Binding<T>> for FieldContext {
769 fn from(binding: Binding<T>) -> Self {
770 Self::new(binding)
771 }
772}
773
774impl<T: 'static> From<Signal<T>> for FieldContext {
775 fn from(signal: Signal<T>) -> Self {
776 Self::new(Binding::<T>::from(signal))
777 }
778}
779
780#[derive(Clone)]
785struct ErasedBinding {
786 binding: Rc<dyn Any>,
787 value_type_name: &'static str,
788 eq: fn(&dyn Any, &dyn Any) -> bool,
789}
790
791impl ErasedBinding {
792 fn new<T: 'static>(binding: Binding<T>) -> Self {
793 Self {
794 binding: Rc::new(binding),
795 value_type_name: std::any::type_name::<T>(),
796 eq: |left, right| match (
797 left.downcast_ref::<Binding<T>>(),
798 right.downcast_ref::<Binding<T>>(),
799 ) {
800 (Some(left), Some(right)) => left == right,
801 _ => false,
802 },
803 }
804 }
805}
806
807impl PartialEq for ErasedBinding {
808 fn eq(&self, other: &Self) -> bool {
809 (self.eq)(&*self.binding, &*other.binding)
810 }
811}
812
813pub fn provide_field_context<T: 'static>(binding: Binding<T>) -> FieldContext {
819 let mut context = FieldContext::new(binding);
820
821 if let Some(existing) = has_context::<FieldContext>() {
822 context = context.with_focus_request(existing.focus_request());
823 }
824
825 provide_context(context)
826}
827
828pub fn use_binding<T: 'static>(explicit: Option<Binding<T>>, default: T) -> Binding<T> {
833 let internal = use_signal(|| default);
834
835 if let Some(binding) = explicit {
836 return binding;
837 }
838
839 if let Some(binding) =
840 try_consume_context::<FieldContext>().and_then(|context| context.try_resolve())
841 {
842 return binding;
843 }
844
845 internal.into()
846}
847
848pub fn use_field_meta(explicit: Option<FieldMeta>) -> FieldMeta {
853 let internal = use_field_meta_state(FieldMetaValues::default());
854
855 explicit
856 .or_else(|| try_consume_context::<FieldContext>().and_then(|context| context.meta()))
857 .unwrap_or(internal)
858}
859
860pub fn use_focus_request() -> FocusRequest {
862 let internal = use_hook(FocusRequest::default);
863
864 try_consume_context::<FieldContext>().map_or(internal, |context| context.focus_request())
865}
866
867pub fn use_focus_registration(callback: Callback<()>) -> FocusRequest {
870 let request = use_focus_request();
871 let active = use_hook(|| Rc::new(RefCell::new(None::<ActiveFocusRegistration>)));
872 let should_replace = active
873 .borrow()
874 .as_ref()
875 .is_none_or(|active| active.request != request || active.callback != callback);
876
877 if should_replace {
878 let registration = request.register(callback);
879 active.borrow_mut().replace(ActiveFocusRegistration {
880 request: request.clone(),
881 callback,
882 _registration: registration,
883 });
884 }
885
886 request
887}
888
889struct ActiveFocusRegistration {
890 request: FocusRequest,
891 callback: Callback<()>,
892 _registration: FocusRegistration,
893}
894
895#[derive(Clone, Debug, Props, PartialEq)]
897pub struct FieldProps {
898 #[props(into)]
903 pub context: FieldContext,
904 #[props(extends = GlobalAttributes)]
906 pub attributes: Vec<Attribute>,
907 pub children: Element,
909}
910
911#[allow(non_snake_case)]
923#[allow(
924 clippy::missing_errors_doc,
925 reason = "Dioxus Element uses Result as its renderer protocol"
926)]
927pub fn Field(props: FieldProps) -> Element {
928 let has_meta_values = props.context.meta_values.is_some();
929 let meta_values = props.context.meta_values.clone().unwrap_or_default();
930 let synced_meta = use_synced_field_meta_state(&meta_values);
931 let mut context = props.context;
932
933 if has_meta_values {
934 context = context.with_meta(synced_meta);
935 }
936
937 let focus_request = use_hook(|| context.focus_request());
938 provide_context(context.with_focus_request(focus_request));
939
940 rsx! {
941 div { ..props.attributes, {props.children} }
942 }
943}
944
945#[derive(Clone, Debug, Props, PartialEq)]
947pub struct LabelProps {
948 #[props(default)]
950 pub meta: Option<FieldMeta>,
951 #[props(default)]
953 pub invalid: Option<bool>,
954 #[props(default)]
956 pub disabled: Option<bool>,
957 #[props(extends = GlobalAttributes)]
959 pub attributes: Vec<Attribute>,
960 pub children: Element,
962}
963
964#[allow(non_snake_case)]
968#[allow(
969 clippy::missing_errors_doc,
970 reason = "Dioxus Element uses Result as its renderer protocol"
971)]
972pub fn Label(props: LabelProps) -> Element {
973 let meta = use_field_meta(props.meta);
974 let control_id = meta.id().map(|id| id.to_string());
975 let mut attributes = part_state_attributes(
976 &meta,
977 FieldMetaOverrides {
978 invalid: props.invalid,
979 disabled: props.disabled,
980 },
981 );
982 attributes.extend(props.attributes);
983
984 rsx! {
985 label { r#for: control_id, ..attributes, {props.children} }
986 }
987}
988
989#[derive(Clone, Debug, Props, PartialEq)]
991pub struct FieldDescriptionProps {
992 #[props(into)]
994 pub id: Rc<str>,
995 #[props(default)]
997 pub meta: Option<FieldMeta>,
998 #[props(default)]
1000 pub invalid: Option<bool>,
1001 #[props(default)]
1003 pub disabled: Option<bool>,
1004 #[props(extends = GlobalAttributes)]
1006 pub attributes: Vec<Attribute>,
1007 pub children: Element,
1009}
1010
1011#[allow(non_snake_case)]
1015#[allow(
1016 clippy::missing_errors_doc,
1017 reason = "Dioxus Element uses Result as its renderer protocol"
1018)]
1019pub fn FieldDescription(props: FieldDescriptionProps) -> Element {
1020 let meta = use_field_meta(props.meta);
1021 use_field_meta_id_registration(&meta, RegisteredIdKind::Description, props.id.clone());
1022 let id = props.id.to_string();
1023 let mut attributes = part_state_attributes(
1024 &meta,
1025 FieldMetaOverrides {
1026 invalid: props.invalid,
1027 disabled: props.disabled,
1028 },
1029 );
1030 attributes.extend(props.attributes);
1031
1032 rsx! {
1033 div { id: id, ..attributes, {props.children} }
1034 }
1035}
1036
1037#[derive(Clone, Debug, Props, PartialEq)]
1039pub struct FieldErrorProps {
1040 #[props(into)]
1042 pub id: Rc<str>,
1043 #[props(default)]
1045 pub meta: Option<FieldMeta>,
1046 #[props(default)]
1048 pub invalid: Option<bool>,
1049 #[props(default)]
1051 pub disabled: Option<bool>,
1052 #[props(extends = GlobalAttributes)]
1054 pub attributes: Vec<Attribute>,
1055}
1056
1057#[allow(non_snake_case)]
1061#[allow(
1062 clippy::missing_errors_doc,
1063 reason = "Dioxus Element uses Result as its renderer protocol"
1064)]
1065pub fn FieldError(props: FieldErrorProps) -> Element {
1066 let meta = use_field_meta(props.meta);
1067 use_field_meta_id_registration(&meta, RegisteredIdKind::Error, props.id.clone());
1068 let invalid = props.invalid.unwrap_or_else(|| meta.invalid());
1069
1070 if !invalid {
1071 return dioxus_core::VNode::empty();
1072 }
1073
1074 let id = props.id.to_string();
1075 let errors = meta
1076 .errors()
1077 .iter()
1078 .map(AsRef::as_ref)
1079 .collect::<Vec<_>>()
1080 .join("\n");
1081 let mut attributes = part_state_attributes(
1082 &meta,
1083 FieldMetaOverrides {
1084 invalid: props.invalid,
1085 disabled: props.disabled,
1086 },
1087 );
1088 attributes.extend(props.attributes);
1089
1090 rsx! {
1091 div {
1092 id: id,
1093 aria_live: "polite",
1094 ..attributes,
1095 {errors}
1096 }
1097 }
1098}
1099
1100fn part_state_attributes(meta: &FieldMeta, overrides: FieldMetaOverrides) -> Vec<Attribute> {
1101 let invalid = overrides.invalid.unwrap_or_else(|| meta.invalid());
1102 let disabled = overrides.disabled.unwrap_or_else(|| meta.disabled());
1103 let mut attributes = Vec::new();
1104
1105 push_data_state(&mut attributes, "data-required", meta.required());
1106 push_data_state(&mut attributes, "data-disabled", disabled);
1107 push_data_state(&mut attributes, "data-invalid", invalid);
1108 push_data_state(&mut attributes, "data-touched", meta.touched());
1109 push_data_state(&mut attributes, "data-dirty", meta.dirty());
1110
1111 attributes
1112}
1113
1114fn use_field_meta_id_registration(meta: &FieldMeta, kind: RegisteredIdKind, id: Rc<str>) {
1115 let active = use_hook(|| Rc::new(RefCell::new(None::<ActiveFieldMetaIdRegistration>)));
1116 let should_replace = active
1117 .borrow()
1118 .as_ref()
1119 .is_none_or(|active| active.meta != *meta || active.kind != kind || active.id != id);
1120
1121 if should_replace {
1122 let mut writable_meta = *meta;
1123 let registration = writable_meta.register_id(kind, id.clone());
1124 active.borrow_mut().replace(ActiveFieldMetaIdRegistration {
1125 meta: *meta,
1126 kind,
1127 id,
1128 _registration: registration,
1129 });
1130 }
1131}
1132
1133struct ActiveFieldMetaIdRegistration {
1134 meta: FieldMeta,
1135 kind: RegisteredIdKind,
1136 id: Rc<str>,
1137 _registration: FieldMetaIdRegistration,
1138}
1139
1140#[derive(Clone)]
1141struct BindingIdentity(Rc<dyn ComparableIdentity>);
1142
1143impl BindingIdentity {
1144 fn new<I: PartialEq + 'static>(identity: I) -> Self {
1145 Self(Rc::new(identity))
1146 }
1147}
1148
1149impl PartialEq for BindingIdentity {
1150 fn eq(&self, other: &Self) -> bool {
1151 self.0.equals(other.0.as_ref())
1152 }
1153}
1154
1155trait ComparableIdentity: Any {
1156 fn equals(&self, other: &dyn ComparableIdentity) -> bool;
1157}
1158
1159impl<I: PartialEq + 'static> ComparableIdentity for I {
1160 fn equals(&self, other: &dyn ComparableIdentity) -> bool {
1161 let other = other as &dyn Any;
1162 other.downcast_ref::<I>().is_some_and(|other| self == other)
1163 }
1164}