@defra/forms-model 3.0.466 → 3.0.468

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -3,10 +3,22 @@ import {
3
3
  type AutocompleteFieldComponent,
4
4
  type CheckboxesFieldComponent,
5
5
  type DatePartsFieldComponent,
6
+ type DetailsComponent,
7
+ type EmailAddressFieldComponent,
6
8
  type FileUploadFieldComponent,
9
+ type HtmlComponent,
10
+ type InsetTextComponent,
11
+ type ListComponent,
12
+ type MarkdownComponent,
13
+ type MonthYearFieldComponent,
14
+ type MultilineTextFieldComponent,
7
15
  type NumberFieldComponent,
8
16
  type RadiosFieldComponent,
9
- type TextFieldComponent
17
+ type SelectFieldComponent,
18
+ type TelephoneNumberFieldComponent,
19
+ type TextFieldComponent,
20
+ type UkAddressFieldComponent,
21
+ type YesNoFieldComponent
10
22
  } from '~/src/components/types.js'
11
23
  import { type Item, type List } from '~/src/form/form-definition/types.js'
12
24
 
@@ -17,19 +29,169 @@ import { type Item, type List } from '~/src/form/form-definition/types.js'
17
29
  export function buildTextFieldComponent(
18
30
  partialTextField: Partial<TextFieldComponent> = {}
19
31
  ): TextFieldComponent {
20
- const textFieldComponent: TextFieldComponent = {
32
+ return {
21
33
  id: '407dd0d7-cce9-4f43-8e1f-7d89cb698875',
22
34
  name: 'TextField',
23
35
  title: 'Text field',
24
- type: ComponentType.TextField,
25
36
  hint: '',
26
37
  options: {},
27
- schema: {}
38
+ schema: {},
39
+ ...partialTextField,
40
+ type: ComponentType.TextField
41
+ }
42
+ }
43
+
44
+ export function buildMultilineTextFieldComponent(
45
+ partialMultilineTextField: Partial<MultilineTextFieldComponent> = {}
46
+ ): MultilineTextFieldComponent {
47
+ return {
48
+ id: '72671f23-552e-4504-a06a-693e240880d5',
49
+ name: 'MuTeCo',
50
+ options: {},
51
+ schema: {},
52
+ title: 'Multiline TextField Component',
53
+ ...partialMultilineTextField,
54
+ type: ComponentType.MultilineTextField
55
+ }
56
+ }
57
+
58
+ export function buildYesNoFieldComponent(
59
+ partialYesNoField: Partial<YesNoFieldComponent> = {}
60
+ ): YesNoFieldComponent {
61
+ return {
62
+ title: 'YesNo Field Component',
63
+ id: 'be7f849c-47d8-4f1f-ba15-ab939dc70914',
64
+ name: 'YesNoFieldComponent',
65
+ options: {},
66
+ ...partialYesNoField,
67
+ type: ComponentType.YesNoField
68
+ }
69
+ }
70
+ export function buildMonthYearFieldComponent(
71
+ partialMonthYearField: Partial<MonthYearFieldComponent> = {}
72
+ ): MonthYearFieldComponent {
73
+ return {
74
+ id: 'd4e99aca-6d13-4c1a-a623-9e9e5b27d46d',
75
+ title: 'MonthYearFieldComponent',
76
+ name: 'MonthYearFieldComponent',
77
+ options: {},
78
+ ...partialMonthYearField,
79
+ type: ComponentType.MonthYearField
80
+ }
81
+ }
82
+ export function buildSelectFieldComponent(
83
+ partialSelectField: Partial<SelectFieldComponent> = {}
84
+ ): SelectFieldComponent {
85
+ return {
86
+ id: '7f219cf6-3e16-4549-b8df-789506682147',
87
+ list: '',
88
+ name: '',
89
+ options: {},
90
+ title: '',
91
+ ...partialSelectField,
92
+ type: ComponentType.SelectField
93
+ }
94
+ }
95
+ export function buildUkAddressFieldComponent(
96
+ partialUkAddressField: Partial<UkAddressFieldComponent> = {}
97
+ ): UkAddressFieldComponent {
98
+ return {
99
+ id: 'a7cb7440-9095-44cd-9136-2914232722c8',
100
+ title: 'UkAddressFieldComponent',
101
+ name: 'UkAddressFieldComponent',
102
+ options: {},
103
+ ...partialUkAddressField,
104
+ type: ComponentType.UkAddressField
105
+ }
106
+ }
107
+ export function buildTelephoneNumberFieldComponent(
108
+ partialTelephoneNumberField: Partial<TelephoneNumberFieldComponent> = {}
109
+ ): TelephoneNumberFieldComponent {
110
+ return {
111
+ id: '69907916-beac-4faa-b469-656dad5edced',
112
+ title: 'TelephoneNumberFieldComponent',
113
+ name: 'TelephoneNumberFieldComponent',
114
+ options: {},
115
+ ...partialTelephoneNumberField,
116
+ type: ComponentType.TelephoneNumberField
28
117
  }
118
+ }
119
+ export function buildEmailAddressFieldComponent(
120
+ partialEmailAddressField: Partial<EmailAddressFieldComponent> = {}
121
+ ): EmailAddressFieldComponent {
122
+ return {
123
+ id: '9dcf0781-bf34-48c8-b13b-d13050dc34d9',
124
+ title: 'EmailAddressFieldComponent',
125
+ name: 'EmailAddressFieldComponent',
126
+ options: {},
127
+ ...partialEmailAddressField,
128
+ type: ComponentType.EmailAddressField
129
+ }
130
+ }
29
131
 
132
+ export function buildHtmlComponent(
133
+ partialHtml: Partial<HtmlComponent> = {}
134
+ ): HtmlComponent {
135
+ return {
136
+ id: 'bac683ce-149e-4740-95aa-8289b35bc327',
137
+ title: 'HtmlComponent',
138
+ name: 'HtmlComponent',
139
+ options: {},
140
+ content: '',
141
+ ...partialHtml,
142
+ type: ComponentType.Html
143
+ }
144
+ }
145
+ export function buildInsetTextComponent(
146
+ partialInsetText: Partial<InsetTextComponent> = {}
147
+ ): InsetTextComponent {
148
+ return {
149
+ id: '6b717151-1e86-42b2-97a9-2201b0676e47',
150
+ title: 'InsetText Component',
151
+ name: 'InsetTextComponent',
152
+ content: '',
153
+ options: {},
154
+ ...partialInsetText,
155
+ type: ComponentType.InsetText
156
+ }
157
+ }
158
+ export function buildDetailsComponent(
159
+ partialDetails: Partial<DetailsComponent> = {}
160
+ ): DetailsComponent {
161
+ return {
162
+ id: '245d54df-bb1e-488e-82f6-8f1e42c197e6',
163
+ title: 'Details Component',
164
+ name: 'DetailsComponent',
165
+ content: '',
166
+ options: {},
167
+ ...partialDetails,
168
+ type: ComponentType.Details
169
+ }
170
+ }
171
+ export function buildListComponent(
172
+ partialList: Partial<ListComponent> = {}
173
+ ): ListComponent {
30
174
  return {
31
- ...textFieldComponent,
32
- ...partialTextField
175
+ id: '62f17168-c2ef-4978-bd42-bdaa5704e25f',
176
+ title: 'List Component',
177
+ name: 'ListComponent',
178
+ list: '',
179
+ options: {},
180
+ ...partialList,
181
+ type: ComponentType.List
182
+ }
183
+ }
184
+ export function buildMarkdownComponent(
185
+ partialMarkdown: Partial<MarkdownComponent> = {}
186
+ ): MarkdownComponent {
187
+ return {
188
+ id: '4a2dc88c-be1a-4277-aff8-04220de2e778',
189
+ title: 'Markdown Component',
190
+ name: 'MarkdownComponent',
191
+ options: {},
192
+ content: '',
193
+ ...partialMarkdown,
194
+ type: ComponentType.Markdown
33
195
  }
34
196
  }
35
197
 
@@ -38,19 +200,15 @@ export function buildTextFieldComponent(
38
200
  * @returns {FileUploadFieldComponent}
39
201
  */
40
202
  export function buildFileUploadComponent(
41
- partialFileUploadField: Partial<FileUploadFieldComponent>
203
+ partialFileUploadField: Partial<FileUploadFieldComponent> = {}
42
204
  ): FileUploadFieldComponent {
43
- const fileUploadFieldComponent: FileUploadFieldComponent = {
205
+ return {
44
206
  name: 'FileUploadField',
45
- type: ComponentType.FileUploadField,
46
207
  title: 'File Upload Field',
47
208
  options: {},
48
- schema: {}
49
- }
50
-
51
- return {
52
- ...fileUploadFieldComponent,
53
- ...partialFileUploadField
209
+ schema: {},
210
+ ...partialFileUploadField,
211
+ type: ComponentType.FileUploadField
54
212
  }
55
213
  }
56
214
 
@@ -60,19 +218,15 @@ export function buildFileUploadComponent(
60
218
  * @returns {AutocompleteFieldComponent}
61
219
  */
62
220
  export function buildAutoCompleteComponent(
63
- partialAutoCompleteField: Partial<AutocompleteFieldComponent>
221
+ partialAutoCompleteField: Partial<AutocompleteFieldComponent> = {}
64
222
  ): AutocompleteFieldComponent {
65
- const autocompleteComponent: AutocompleteFieldComponent = {
223
+ return {
66
224
  name: 'AutoCompleteField',
67
225
  title: 'What languages do you speak?',
68
- type: ComponentType.AutocompleteField,
69
226
  list: 'AutoCompleteList',
70
- options: {}
71
- }
72
-
73
- return {
74
- ...autocompleteComponent,
75
- ...partialAutoCompleteField
227
+ options: {},
228
+ ...partialAutoCompleteField,
229
+ type: ComponentType.AutocompleteField
76
230
  }
77
231
  }
78
232
 
@@ -83,17 +237,13 @@ export function buildAutoCompleteComponent(
83
237
  export function buildRadioComponent(
84
238
  partialListComponent: Partial<RadiosFieldComponent> = {}
85
239
  ): RadiosFieldComponent {
86
- const radioFieldComponent: RadiosFieldComponent = {
240
+ return {
87
241
  name: 'RadioField',
88
242
  title: 'Which country do you live in?',
89
- type: ComponentType.RadiosField,
90
243
  list: 'RadioList',
91
- options: {}
92
- }
93
-
94
- return {
95
- ...radioFieldComponent,
96
- ...partialListComponent
244
+ options: {},
245
+ ...partialListComponent,
246
+ type: ComponentType.RadiosField
97
247
  }
98
248
  }
99
249
 
@@ -102,19 +252,15 @@ export function buildRadioComponent(
102
252
  * @returns {CheckboxesFieldComponent}
103
253
  */
104
254
  export function buildCheckboxComponent(
105
- partialListComponent: Partial<CheckboxesFieldComponent>
255
+ partialListComponent: Partial<CheckboxesFieldComponent> = {}
106
256
  ): CheckboxesFieldComponent {
107
- const checkboxesFieldComponent: CheckboxesFieldComponent = {
257
+ return {
108
258
  name: 'FellowshipOfTheRing',
109
259
  title: 'Which are your favourite characters from the fellowship?',
110
- type: ComponentType.CheckboxesField,
111
260
  list: 'CheckboxList',
112
- options: {}
113
- }
114
-
115
- return {
116
- ...checkboxesFieldComponent,
117
- ...partialListComponent
261
+ options: {},
262
+ ...partialListComponent,
263
+ type: ComponentType.CheckboxesField
118
264
  }
119
265
  }
120
266
 
@@ -171,7 +317,7 @@ export function buildList(partialList: Partial<List> = {}): List {
171
317
  }
172
318
 
173
319
  export function buildNumberFieldComponent(
174
- partialComponent: Partial<NumberFieldComponent>
320
+ partialComponent: Partial<NumberFieldComponent> = {}
175
321
  ): NumberFieldComponent {
176
322
  return {
177
323
  name: 'year',
@@ -184,7 +330,7 @@ export function buildNumberFieldComponent(
184
330
  }
185
331
 
186
332
  export function buildDateComponent(
187
- partialComponent: Partial<DatePartsFieldComponent>
333
+ partialComponent: Partial<DatePartsFieldComponent> = {}
188
334
  ): DatePartsFieldComponent {
189
335
  return {
190
336
  name: 'bcdefg',
@@ -196,7 +342,7 @@ export function buildDateComponent(
196
342
  }
197
343
 
198
344
  export function buildRadiosComponent(
199
- partialComponent: Partial<RadiosFieldComponent>
345
+ partialComponent: Partial<RadiosFieldComponent> = {}
200
346
  ): RadiosFieldComponent {
201
347
  return {
202
348
  name: 'cdefgh',
@@ -33,13 +33,13 @@ export function buildQuestionPage(
33
33
  export function buildSummaryPage(
34
34
  partialSummaryPage: Partial<PageSummary> = {}
35
35
  ): PageSummary {
36
- const pageSummary: PageSummary = {
36
+ return {
37
37
  id: '449a45f6-4541-4a46-91bd-8b8931b07b50',
38
38
  title: 'Summary page',
39
+ ...partialSummaryPage,
39
40
  path: ControllerPath.Summary,
40
41
  controller: ControllerType.Summary
41
42
  }
42
- return { ...pageSummary, ...partialSummaryPage }
43
43
  }
44
44
 
45
45
  /**
@@ -50,7 +50,7 @@ export function buildSummaryPage(
50
50
  export function buildFileUploadPage(
51
51
  partialFileUploadPage: Partial<PageFileUpload> = {}
52
52
  ): PageFileUpload {
53
- const fileUploadPage: PageFileUpload = {
53
+ return {
54
54
  id: '85e5c8da-88f5-4009-a821-7d7de1364318',
55
55
  title: '',
56
56
  path: '/supporting-evidence',
@@ -69,13 +69,9 @@ export function buildFileUploadPage(
69
69
  id: '4189b8a1-1a04-4f74-a7a0-dd23012a0ee0'
70
70
  })
71
71
  ],
72
- controller: ControllerType.FileUpload,
73
- next: []
74
- }
75
-
76
- return {
77
- ...fileUploadPage,
78
- ...partialFileUploadPage
72
+ next: [],
73
+ ...partialFileUploadPage,
74
+ controller: ControllerType.FileUpload
79
75
  }
80
76
  }
81
77
 
@@ -87,7 +83,7 @@ export function buildFileUploadPage(
87
83
  export function buildRepeaterPage(
88
84
  partialRepeaterPage: Partial<PageRepeat> = {}
89
85
  ): PageRepeat {
90
- const repeaterPage: PageRepeat = {
86
+ return {
91
87
  title: 'Repeater Page',
92
88
  path: '/repeater-page',
93
89
  components: [
@@ -104,15 +100,11 @@ export function buildRepeaterPage(
104
100
  ],
105
101
  next: [],
106
102
  id: '32888028-61db-40fc-b255-80bc67829d31',
107
- controller: ControllerType.Repeat,
108
103
  repeat: {
109
104
  options: { name: 'fawfed', title: 'Simple question responses' },
110
105
  schema: { min: 1, max: 3 }
111
- }
112
- }
113
-
114
- return {
115
- ...repeaterPage,
116
- ...partialRepeaterPage
106
+ },
107
+ ...partialRepeaterPage,
108
+ controller: ControllerType.Repeat
117
109
  }
118
110
  }
@@ -2,7 +2,11 @@ import Joi, { type LanguageMessages } from 'joi'
2
2
  import { v4 as uuidV4 } from 'uuid'
3
3
 
4
4
  import { ComponentType } from '~/src/components/enums.js'
5
- import { type ComponentDef } from '~/src/components/types.js'
5
+ import {
6
+ type ComponentDef,
7
+ type ContentComponentsDef,
8
+ type FileUploadFieldComponent
9
+ } from '~/src/components/types.js'
6
10
  import {
7
11
  type ConditionData,
8
12
  type ConditionDataV2,
@@ -18,6 +22,7 @@ import {
18
22
  type RelativeDateValueData
19
23
  } from '~/src/conditions/types.js'
20
24
  import {
25
+ SchemaVersion,
21
26
  type ConditionWrapper,
22
27
  type ConditionWrapperV2,
23
28
  type Event,
@@ -34,6 +39,7 @@ import {
34
39
  type RepeatSchema,
35
40
  type Section
36
41
  } from '~/src/form/form-definition/types.js'
42
+ import { ControllerType } from '~/src/pages/enums.js'
37
43
  import { hasComponents } from '~/src/pages/helpers.js'
38
44
 
39
45
  const idSchemaOptional = Joi.string().uuid()
@@ -449,6 +455,26 @@ export const componentSchemaV2 = componentSchema
449
455
  })
450
456
  .description('Component schema for V2 forms')
451
457
 
458
+ export const fileUploadComponentSchema = componentSchemaV2.keys({
459
+ type: Joi.string<ComponentType.FileUploadField>()
460
+ .trim()
461
+ .valid(ComponentType.FileUploadField)
462
+ .required()
463
+ .description('Component that can only be a FileUploadField')
464
+ })
465
+
466
+ export const contentComponentSchema = componentSchemaV2.keys({
467
+ type: Joi.string<ComponentType>()
468
+ .trim()
469
+ .valid(ComponentType.Details)
470
+ .valid(ComponentType.Html)
471
+ .valid(ComponentType.Markdown)
472
+ .valid(ComponentType.InsetText)
473
+ .valid(ComponentType.List)
474
+ .required()
475
+ .description('Content only component type (Details, Html, Markdown, etc.)')
476
+ })
477
+
452
478
  const nextSchema = Joi.object<Link>()
453
479
  .description('Navigation link defining where to go after completing a page')
454
480
  .keys({
@@ -549,6 +575,19 @@ const eventsSchema = Joi.object<Events>()
549
575
  .description('Event handler triggered when the page data is saved')
550
576
  })
551
577
 
578
+ export const pageUploadComponentsSchema = Joi.array<
579
+ FileUploadFieldComponent | ContentComponentsDef
580
+ >()
581
+ .items(
582
+ fileUploadComponentSchema.required(),
583
+ contentComponentSchema.optional()
584
+ )
585
+ .unique('name')
586
+ .unique('id')
587
+ .min(1)
588
+ .max(2)
589
+ .description('Components allowed on Page Upload schema')
590
+
552
591
  /**
553
592
  * `/status` is a special route for providing a user's application status.
554
593
  * It should not be configured via the designer.
@@ -619,11 +658,19 @@ export const pageSchemaV2 = pageSchema
619
658
  .description(
620
659
  'Page title displayed at the top of the page (with support for empty titles in V2)'
621
660
  ),
622
- components: Joi.array<ComponentDef>()
623
- .items(componentSchemaV2)
624
- .unique('name')
625
- .unique('id')
626
- .description('Components schema for V2 forms'),
661
+ components: Joi.when('controller', {
662
+ switch: [
663
+ {
664
+ is: Joi.string().trim().valid(ControllerType.FileUpload).required(),
665
+ then: pageUploadComponentsSchema
666
+ }
667
+ ],
668
+ otherwise: Joi.array<ComponentDef>()
669
+ .items(componentSchemaV2)
670
+ .unique('name')
671
+ .unique('id')
672
+ .description('Components schema for V2 forms')
673
+ }),
627
674
  condition: Joi.string()
628
675
  .trim()
629
676
  .valid(conditionIdRef)
@@ -796,6 +843,11 @@ export const formDefinitionSchema = Joi.object<FormDefinition>()
796
843
  .allow('V1', 'V2')
797
844
  .default('V1')
798
845
  .description('Form engine version to use (V1 or V2)'),
846
+ schema: Joi.number()
847
+ .integer()
848
+ .valid(SchemaVersion.V1)
849
+ .default(SchemaVersion.V1)
850
+ .description('Form schema version to use (1 or 2)'),
799
851
  name: Joi.string()
800
852
  .trim()
801
853
  .allow('')
@@ -856,6 +908,10 @@ export const formDefinitionSchema = Joi.object<FormDefinition>()
856
908
 
857
909
  export const formDefinitionV2Schema = formDefinitionSchema
858
910
  .keys({
911
+ schema: Joi.number()
912
+ .integer()
913
+ .valid(SchemaVersion.V2)
914
+ .description('Form schema version to use (2)'),
859
915
  pages: Joi.array<Page>()
860
916
  .items(pageSchemaV2)
861
917
  .required()
@@ -11,6 +11,11 @@ export enum Engine {
11
11
  V2 = 'V2'
12
12
  }
13
13
 
14
+ export enum SchemaVersion {
15
+ V1 = 1,
16
+ V2 = 2
17
+ }
18
+
14
19
  export interface Link {
15
20
  path: string
16
21
  condition?: string
@@ -174,6 +179,7 @@ export interface ConditionWrapperV2 {
174
179
  */
175
180
  export interface FormDefinition {
176
181
  engine?: Engine
182
+ schema?: SchemaVersion
177
183
  pages: Page[]
178
184
  conditions: (ConditionWrapper | ConditionWrapperV2)[]
179
185
  lists: List[]