1use crate::{
3 ComponentInstanceId, ComponentInstancePlan, FieldId, FormEntity, FormFieldDirtySlotId,
4 FormFieldEntity, FormFieldTouchedSlotId, FormFieldValidationSlotId, FormFieldValueSlotId,
5 FormId, FormInstanceId, FormSubmissionStateSlotId, FormValidationAggregateSlotId,
6};
7use std::collections::BTreeMap;
8
9#[derive(Debug, Clone, Copy, PartialEq, Eq)]
10pub enum FormIrOperation {
11 InitializeFormInstance,
12 InitializeFieldValue,
13 InitializeFieldDirty,
14 InitializeFieldTouched,
15 InitializeFieldValidation,
16 ReadControlChannel,
17 NormalizeControlValue,
18 WriteFieldValue,
19 WriteControlChannel,
20 ComputeDirty,
21 MarkTouched,
22 EvaluateValidationRule,
23 StoreValidationResult,
24 ComputeFormValidity,
25 SerializeForm,
26 BeginSubmission,
27 InvokeSubmissionAction,
28 CompleteSubmission,
29 FailSubmission,
30 ResetField,
31 ResetForm,
32 ClearValidation,
33}
34
35#[derive(Debug, Clone, PartialEq, Eq)]
36pub struct FormRuntimeStorage {
37 pub value: BTreeMap<FieldId, FormFieldValueSlotId>,
38 pub dirty: BTreeMap<FieldId, FormFieldDirtySlotId>,
39 pub touched: BTreeMap<FieldId, FormFieldTouchedSlotId>,
40 pub validation: BTreeMap<FieldId, FormFieldValidationSlotId>,
41 pub aggregate: FormValidationAggregateSlotId,
42 pub submission: FormSubmissionStateSlotId,
43}
44#[derive(Debug, Clone, PartialEq, Eq)]
45pub struct FormInstanceIr {
46 pub id: FormInstanceId,
47 pub form: FormId,
48 pub component_instance: ComponentInstanceId,
49 pub storage: FormRuntimeStorage,
50 pub initialize: Vec<FormIrOperation>,
51 pub input: BTreeMap<FieldId, Vec<FormIrOperation>>,
52 pub blur: BTreeMap<FieldId, Vec<FormIrOperation>>,
53 pub reset: Vec<FormIrOperation>,
54}
55#[derive(Debug, Clone, PartialEq, Eq, Default)]
56pub struct FormIrReport {
57 pub instances: BTreeMap<FormInstanceId, FormInstanceIr>,
58}
59
60#[must_use]
61#[allow(clippy::too_many_lines)]
62pub fn lower_form_ir(
63 instances: &ComponentInstancePlan,
64 forms: &BTreeMap<FormId, FormEntity>,
65 fields: &BTreeMap<FieldId, FormFieldEntity>,
66) -> FormIrReport {
67 let mut result = BTreeMap::new();
68 for component in instances.instances.values() {
69 for form in forms
70 .values()
71 .filter(|form| form.owner.entity_id() == Some(&component.component))
72 {
73 let id = FormInstanceId::for_component_instance(&component.id, &form.id);
74 let mut form_fields = fields
75 .values()
76 .filter(|field| field.owner_form == form.id)
77 .collect::<Vec<_>>();
78 form_fields
79 .sort_by(|a, b| (a.declaration_order, &a.id).cmp(&(b.declaration_order, &b.id)));
80 let storage = FormRuntimeStorage {
81 value: form_fields
82 .iter()
83 .map(|f| {
84 (
85 f.id.clone(),
86 FormFieldValueSlotId::for_instance(&id, f.id.as_str()),
87 )
88 })
89 .collect(),
90 dirty: form_fields
91 .iter()
92 .map(|f| {
93 (
94 f.id.clone(),
95 FormFieldDirtySlotId::for_instance(&id, f.id.as_str()),
96 )
97 })
98 .collect(),
99 touched: form_fields
100 .iter()
101 .map(|f| {
102 (
103 f.id.clone(),
104 FormFieldTouchedSlotId::for_instance(&id, f.id.as_str()),
105 )
106 })
107 .collect(),
108 validation: form_fields
109 .iter()
110 .map(|f| {
111 (
112 f.id.clone(),
113 FormFieldValidationSlotId::for_instance(&id, f.id.as_str()),
114 )
115 })
116 .collect(),
117 aggregate: FormValidationAggregateSlotId::for_instance(&id, "aggregate"),
118 submission: FormSubmissionStateSlotId::for_instance(&id, "submission"),
119 };
120 let input = form_fields
121 .iter()
122 .map(|f| {
123 (
124 f.id.clone(),
125 vec![
126 FormIrOperation::ReadControlChannel,
127 FormIrOperation::NormalizeControlValue,
128 FormIrOperation::WriteFieldValue,
129 FormIrOperation::ComputeDirty,
130 FormIrOperation::EvaluateValidationRule,
131 FormIrOperation::StoreValidationResult,
132 FormIrOperation::ComputeFormValidity,
133 FormIrOperation::WriteControlChannel,
134 ],
135 )
136 })
137 .collect();
138 let blur = form_fields
139 .iter()
140 .map(|f| {
141 (
142 f.id.clone(),
143 vec![
144 FormIrOperation::MarkTouched,
145 FormIrOperation::EvaluateValidationRule,
146 FormIrOperation::ComputeFormValidity,
147 ],
148 )
149 })
150 .collect();
151 result.insert(
152 id.clone(),
153 FormInstanceIr {
154 id,
155 form: form.id.clone(),
156 component_instance: component.id.clone(),
157 storage,
158 initialize: vec![FormIrOperation::InitializeFormInstance],
159 input,
160 blur,
161 reset: vec![
162 FormIrOperation::ResetField,
163 FormIrOperation::ClearValidation,
164 FormIrOperation::ResetForm,
165 ],
166 },
167 );
168 }
169 }
170 FormIrReport { instances: result }
171}
172
173#[cfg(test)]
174mod tests {
175 #[test]
176 fn repeats_form_storage_per_component_instance() {
177 let parsed = presolve_parser::parse_file(
178 "src/X.tsx",
179 r#"@component("x") class X { @form() form!: Form; @field(this.form) value=""; render(){return <input field={this.value}/>;} }"#,
180 );
181 let model = crate::build_application_semantic_model(&parsed);
182 assert_eq!(
183 model.form_ir.instances.len(),
184 model.component_instance_plan.instances.len()
185 );
186 assert!(model
187 .form_ir
188 .instances
189 .values()
190 .all(|instance| !instance.storage.value.is_empty()));
191 }
192}