@defra/forms-model 3.0.686 → 3.0.688

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.
Files changed (66) hide show
  1. package/dist/module/__stubs__/examples.js +185 -0
  2. package/dist/module/__stubs__/examples.js.map +1 -0
  3. package/dist/module/components/helpers.js +4 -0
  4. package/dist/module/components/helpers.js.map +1 -1
  5. package/dist/module/components/types.js.map +1 -1
  6. package/dist/module/form/form-definition/index.js +6 -0
  7. package/dist/module/form/form-definition/index.js.map +1 -1
  8. package/dist/module/form/form-editor/index.js +2 -0
  9. package/dist/module/form/form-editor/index.js.map +1 -1
  10. package/dist/module/form/form-editor/translations/translations-config.js +168 -0
  11. package/dist/module/form/form-editor/translations/translations-config.js.map +1 -0
  12. package/dist/module/form/form-editor/translations/translations-overview.js +93 -0
  13. package/dist/module/form/form-editor/translations/translations-overview.js.map +1 -0
  14. package/dist/module/form/form-editor/translations/translations.js +214 -0
  15. package/dist/module/form/form-editor/translations/translations.js.map +1 -0
  16. package/dist/module/form/form-editor/types.js.map +1 -1
  17. package/dist/module/form/utils/list.js +23 -0
  18. package/dist/module/form/utils/list.js.map +1 -1
  19. package/dist/module/form/utils/numbering.js +43 -0
  20. package/dist/module/form/utils/numbering.js.map +1 -0
  21. package/dist/module/index.js +2 -0
  22. package/dist/module/index.js.map +1 -1
  23. package/dist/types/__stubs__/examples.d.ts +9 -0
  24. package/dist/types/__stubs__/examples.d.ts.map +1 -0
  25. package/dist/types/components/helpers.d.ts +2 -1
  26. package/dist/types/components/helpers.d.ts.map +1 -1
  27. package/dist/types/components/types.d.ts +8 -0
  28. package/dist/types/components/types.d.ts.map +1 -1
  29. package/dist/types/form/form-definition/index.d.ts.map +1 -1
  30. package/dist/types/form/form-editor/index.d.ts +2 -0
  31. package/dist/types/form/form-editor/index.d.ts.map +1 -1
  32. package/dist/types/form/form-editor/translations/translations-config.d.ts +95 -0
  33. package/dist/types/form/form-editor/translations/translations-config.d.ts.map +1 -0
  34. package/dist/types/form/form-editor/translations/translations-overview.d.ts +14 -0
  35. package/dist/types/form/form-editor/translations/translations-overview.d.ts.map +1 -0
  36. package/dist/types/form/form-editor/translations/translations.d.ts +16 -0
  37. package/dist/types/form/form-editor/translations/translations.d.ts.map +1 -0
  38. package/dist/types/form/form-editor/types.d.ts +5 -1
  39. package/dist/types/form/form-editor/types.d.ts.map +1 -1
  40. package/dist/types/form/utils/list.d.ts +8 -1
  41. package/dist/types/form/utils/list.d.ts.map +1 -1
  42. package/dist/types/form/utils/numbering.d.ts +18 -0
  43. package/dist/types/form/utils/numbering.d.ts.map +1 -0
  44. package/dist/types/index.d.ts +2 -0
  45. package/dist/types/index.d.ts.map +1 -1
  46. package/package.json +1 -1
  47. package/schemas/component-schema-v2.json +13 -0
  48. package/schemas/component-schema.json +13 -0
  49. package/schemas/form-definition-schema.json +39 -0
  50. package/schemas/form-definition-v2-schema.json +65 -0
  51. package/schemas/list-schema-v2.json +26 -0
  52. package/schemas/list-schema.json +26 -0
  53. package/schemas/page-schema-v2.json +39 -0
  54. package/schemas/page-schema.json +13 -0
  55. package/src/__stubs__/examples.js +231 -0
  56. package/src/components/helpers.ts +14 -0
  57. package/src/components/types.ts +15 -0
  58. package/src/form/form-definition/index.ts +19 -0
  59. package/src/form/form-editor/index.ts +6 -0
  60. package/src/form/form-editor/translations/translations-config.js +186 -0
  61. package/src/form/form-editor/translations/translations-overview.js +137 -0
  62. package/src/form/form-editor/translations/translations.js +297 -0
  63. package/src/form/form-editor/types.ts +6 -0
  64. package/src/form/utils/list.ts +34 -1
  65. package/src/form/utils/numbering.js +47 -0
  66. package/src/index.ts +2 -0
@@ -385,6 +385,19 @@
385
385
  },
386
386
  "title": "Geometry Types"
387
387
  },
388
+ "mapLayers": {
389
+ "type": "object",
390
+ "description": "Supported map layers",
391
+ "properties": {
392
+ "sssi": {
393
+ "type": "boolean",
394
+ "description": "Sites of Special Scientific Interest layer",
395
+ "title": "Sssi"
396
+ }
397
+ },
398
+ "additionalProperties": false,
399
+ "title": "Map Layers"
400
+ },
388
401
  "format": {
389
402
  "type": "string",
390
403
  "description": "Format for telephone number - for TelephoneNumberField only",
@@ -680,6 +693,19 @@
680
693
  },
681
694
  "title": "Geometry Types"
682
695
  },
696
+ "mapLayers": {
697
+ "type": "object",
698
+ "description": "Supported map layers",
699
+ "properties": {
700
+ "sssi": {
701
+ "type": "boolean",
702
+ "description": "Sites of Special Scientific Interest layer",
703
+ "title": "Sssi"
704
+ }
705
+ },
706
+ "additionalProperties": false,
707
+ "title": "Map Layers"
708
+ },
683
709
  "format": {
684
710
  "type": "string",
685
711
  "description": "Format for telephone number - for TelephoneNumberField only",
@@ -1059,6 +1085,19 @@
1059
1085
  },
1060
1086
  "title": "Geometry Types"
1061
1087
  },
1088
+ "mapLayers": {
1089
+ "type": "object",
1090
+ "description": "Supported map layers",
1091
+ "properties": {
1092
+ "sssi": {
1093
+ "type": "boolean",
1094
+ "description": "Sites of Special Scientific Interest layer",
1095
+ "title": "Sssi"
1096
+ }
1097
+ },
1098
+ "additionalProperties": false,
1099
+ "title": "Map Layers"
1100
+ },
1062
1101
  "format": {
1063
1102
  "type": "string",
1064
1103
  "description": "Format for telephone number - for TelephoneNumberField only",
@@ -248,6 +248,19 @@
248
248
  },
249
249
  "title": "Geometry Types"
250
250
  },
251
+ "mapLayers": {
252
+ "type": "object",
253
+ "description": "Supported map layers",
254
+ "properties": {
255
+ "sssi": {
256
+ "type": "boolean",
257
+ "description": "Sites of Special Scientific Interest layer",
258
+ "title": "Sssi"
259
+ }
260
+ },
261
+ "additionalProperties": false,
262
+ "title": "Map Layers"
263
+ },
251
264
  "format": {
252
265
  "type": "string",
253
266
  "description": "Format for telephone number - for TelephoneNumberField only",
@@ -0,0 +1,231 @@
1
+ import {
2
+ buildDeclarationFieldComponent,
3
+ buildLatLongFieldComponent,
4
+ buildMarkdownComponent,
5
+ buildRadiosComponent,
6
+ buildTextFieldComponent
7
+ } from '~/src/__stubs__/components.js'
8
+ import { buildDefinition } from '~/src/__stubs__/form-definition.js'
9
+ import {
10
+ buildQuestionPage,
11
+ buildRepeaterPage,
12
+ buildSummaryPage
13
+ } from '~/src/__stubs__/pages.js'
14
+
15
+ export const formDefinitionWithSinglePage = buildDefinition({
16
+ pages: [
17
+ buildQuestionPage({
18
+ id: 'p1',
19
+ section: 'section',
20
+ components: [
21
+ buildTextFieldComponent({
22
+ id: 'c1',
23
+ name: 'textField',
24
+ title: 'This is your first field',
25
+ hint: 'Help text'
26
+ })
27
+ ],
28
+ next: [{ path: '/summary' }]
29
+ }),
30
+ buildSummaryPage({
31
+ id: 'c2'
32
+ })
33
+ ]
34
+ })
35
+
36
+ export const formDefinitionWithoutSummary = buildDefinition({
37
+ pages: [
38
+ buildQuestionPage({
39
+ id: 'p1',
40
+ section: 'section',
41
+ components: [
42
+ buildTextFieldComponent({
43
+ id: 'c1',
44
+ name: 'textField',
45
+ title: 'This is your first field',
46
+ hint: 'Help text'
47
+ })
48
+ ],
49
+ next: [{ path: '/summary' }]
50
+ })
51
+ ]
52
+ })
53
+
54
+ export const formDefinitionWithTwoQuestions = buildDefinition({
55
+ pages: [
56
+ buildQuestionPage({
57
+ id: 'p1',
58
+ section: 'section',
59
+ components: [
60
+ buildTextFieldComponent({
61
+ id: 'q1',
62
+ name: 'textField',
63
+ title: 'This is your first question',
64
+ hint: 'Help text'
65
+ }),
66
+ buildTextFieldComponent({
67
+ id: 'q2',
68
+ name: 'textField',
69
+ title: 'This is your second question',
70
+ hint: 'Help text'
71
+ })
72
+ ],
73
+ next: [{ path: '/summary' }]
74
+ }),
75
+ buildSummaryPage({
76
+ id: 'p2'
77
+ })
78
+ ]
79
+ })
80
+
81
+ export const formDefinitionWithTwoQuestionsAndGuidance = buildDefinition({
82
+ pages: [
83
+ buildQuestionPage({
84
+ id: 'p1',
85
+ section: 'section',
86
+ components: [
87
+ buildMarkdownComponent({ content: 'My markdown text' }),
88
+ buildTextFieldComponent({
89
+ id: 'q1',
90
+ name: 'textField',
91
+ title: 'This is the first textfield question',
92
+ hint: 'Help text'
93
+ }),
94
+ buildTextFieldComponent({
95
+ id: 'q2',
96
+ name: 'textField',
97
+ title: 'This is the second textfield question',
98
+ hint: 'Help text'
99
+ })
100
+ ],
101
+ next: [{ path: '/summary' }]
102
+ }),
103
+ buildSummaryPage({
104
+ id: 'p2'
105
+ })
106
+ ]
107
+ })
108
+
109
+ export const formDefinitionWithLocationAndDeclaration = buildDefinition({
110
+ pages: [
111
+ buildQuestionPage({
112
+ id: 'p1',
113
+ section: 'section',
114
+ components: [
115
+ buildLatLongFieldComponent({
116
+ id: 'q1',
117
+ name: 'latLongField',
118
+ title: 'This is the first lat long field question',
119
+ hint: 'Hint text',
120
+ options: {
121
+ instructionText: 'Some instruction text'
122
+ }
123
+ }),
124
+ buildDeclarationFieldComponent({
125
+ id: 'q2',
126
+ name: 'declarationField',
127
+ title: 'This is a declaration question',
128
+ hint: 'Declaration hint text',
129
+ content: 'This is the declaration that needs agreeing to',
130
+ shortDescription: 'Declaration short desc'
131
+ })
132
+ ],
133
+ next: [{ path: '/summary' }]
134
+ }),
135
+ buildSummaryPage({
136
+ id: 'p2'
137
+ })
138
+ ]
139
+ })
140
+
141
+ export const formDefinitionWithRepeater = buildDefinition({
142
+ pages: [
143
+ buildRepeaterPage({
144
+ id: 'p1',
145
+ section: 'section',
146
+ components: [
147
+ buildTextFieldComponent({
148
+ id: 'q1',
149
+ name: 'TextField',
150
+ title: 'This is the first text field field question',
151
+ hint: 'Hint text',
152
+ shortDescription: 'Short desc'
153
+ })
154
+ ],
155
+ next: [{ path: '/summary' }]
156
+ }),
157
+ buildSummaryPage({
158
+ id: 'p2'
159
+ })
160
+ ]
161
+ })
162
+
163
+ export const formDefinitionWithNoQuestions = buildDefinition({
164
+ pages: [
165
+ buildQuestionPage({
166
+ id: 'p1',
167
+ section: 'section',
168
+ components: [],
169
+ next: [{ path: '/summary' }]
170
+ }),
171
+ buildSummaryPage({
172
+ id: 'p2'
173
+ })
174
+ ]
175
+ })
176
+
177
+ export const formDefinitionWithRadioQuestion = buildDefinition({
178
+ pages: [
179
+ buildQuestionPage({
180
+ id: 'p1',
181
+ section: 'section',
182
+ components: [
183
+ buildRadiosComponent({
184
+ id: 'q1',
185
+ name: 'radioField',
186
+ title: 'This is a radio question',
187
+ hint: 'Help text',
188
+ list: 'my-list-id'
189
+ })
190
+ ],
191
+ next: [{ path: '/summary' }]
192
+ }),
193
+ buildSummaryPage({
194
+ id: 'p2'
195
+ })
196
+ ],
197
+ lists: [
198
+ {
199
+ name: 'my-list',
200
+ id: 'my-list-id',
201
+ type: 'string',
202
+ title: 'list for radio',
203
+ items: [
204
+ {
205
+ id: 'option-1',
206
+ text: 'Option 1',
207
+ hint: {
208
+ text: 'Option 1 hint'
209
+ },
210
+ value: 'Option1'
211
+ },
212
+ {
213
+ id: 'option-2',
214
+ text: 'Option 2',
215
+ hint: {
216
+ text: 'Option 2 hint'
217
+ },
218
+ value: 'Option2'
219
+ },
220
+ {
221
+ id: 'option-3',
222
+ text: 'Option 3',
223
+ hint: {
224
+ text: 'Option 3 hint'
225
+ },
226
+ value: 'Option3'
227
+ }
228
+ ]
229
+ }
230
+ ]
231
+ })
@@ -2,6 +2,7 @@ import { ComponentTypes } from '~/src/components/component-types.js'
2
2
  import { ComponentType } from '~/src/components/enums.js'
3
3
  import {
4
4
  type ComponentDef,
5
+ type ComponentsDefWithInstructions,
5
6
  type ConditionalComponentType,
6
7
  type ConditionalComponentsDef,
7
8
  type ContentComponentsDef,
@@ -145,6 +146,19 @@ export function isListType(
145
146
  return !!type && allowedTypes.includes(type)
146
147
  }
147
148
 
149
+ export function isTypeWithInstructions(
150
+ type?: ComponentType
151
+ ): type is ComponentsDefWithInstructions['type'] {
152
+ const allowedTypes = [
153
+ ComponentType.EastingNorthingField,
154
+ ComponentType.OsGridRefField,
155
+ ComponentType.NationalGridFieldNumberField,
156
+ ComponentType.LatLongField
157
+ ]
158
+
159
+ return !!type && allowedTypes.includes(type)
160
+ }
161
+
148
162
  /**
149
163
  * Filter known form components with input fields
150
164
  * (excludes content and selection fields)
@@ -188,12 +188,17 @@ export interface UkAddressFieldComponent extends FormFieldBase {
188
188
  }
189
189
  }
190
190
 
191
+ export interface MapLayerOptions {
192
+ sssi?: boolean
193
+ }
194
+
191
195
  // Precise Location Fields
192
196
  export interface EastingNorthingFieldComponent extends FormFieldBase {
193
197
  type: ComponentType.EastingNorthingField
194
198
  options: FormFieldBase['options'] & {
195
199
  condition?: string
196
200
  customValidationMessage?: string
201
+ mapLayers?: MapLayerOptions
197
202
  }
198
203
  schema?: {
199
204
  easting?: {
@@ -212,6 +217,7 @@ export interface OsGridRefFieldComponent extends FormFieldBase {
212
217
  options: FormFieldBase['options'] & {
213
218
  condition?: string
214
219
  customValidationMessage?: string
220
+ mapLayers?: MapLayerOptions
215
221
  }
216
222
  }
217
223
 
@@ -228,6 +234,7 @@ export interface LatLongFieldComponent extends FormFieldBase {
228
234
  options: FormFieldBase['options'] & {
229
235
  condition?: string
230
236
  customValidationMessage?: string
237
+ mapLayers?: MapLayerOptions
231
238
  }
232
239
  schema?: {
233
240
  latitude?: {
@@ -290,6 +297,7 @@ export interface GeospatialFieldComponent extends FormFieldBase {
290
297
  condition?: string
291
298
  countries?: GeospatialFieldOptionsCountry[]
292
299
  geometryTypes?: GeospatialFieldGeometryTypesEnum[]
300
+ mapLayers?: MapLayerOptions
293
301
  }
294
302
  schema?: {
295
303
  max?: number
@@ -435,6 +443,13 @@ export type SelectionComponentsDef =
435
443
  | SelectFieldComponent
436
444
  | YesNoFieldComponent
437
445
 
446
+ // Components that have instruction fields
447
+ export type ComponentsDefWithInstructions =
448
+ | EastingNorthingFieldComponent
449
+ | OsGridRefFieldComponent
450
+ | NationalGridFieldNumberFieldComponent
451
+ | LatLongFieldComponent
452
+
438
453
  // Components that have condition support
439
454
  export type ConditionalComponentsDef = Exclude<
440
455
  ComponentDef,
@@ -680,6 +680,25 @@ export const componentSchema = Joi.object<ComponentDef>()
680
680
  'Geometry types for geospatial data - for GeospatialField only'
681
681
  )
682
682
  }).description('Geometry types - for GeospatialField only'),
683
+ mapLayers: Joi.when('type', {
684
+ is: Joi.string()
685
+ .trim()
686
+ .valid(
687
+ ComponentType.GeospatialField,
688
+ ComponentType.EastingNorthingField,
689
+ ComponentType.LatLongField,
690
+ ComponentType.OsGridRefField
691
+ )
692
+ .required(),
693
+ then: Joi.object()
694
+ .keys({
695
+ sssi: Joi.boolean()
696
+ .optional()
697
+ .description('Sites of Special Scientific Interest layer')
698
+ })
699
+ .optional()
700
+ .description('Supported map layers')
701
+ }).description('Map layers'),
683
702
  format: Joi.when('type', {
684
703
  is: Joi.string()
685
704
  .trim()
@@ -471,6 +471,11 @@ export const countriesSchema = Joi.array()
471
471
  .single()
472
472
  .description('The country to be included in a geospatial field')
473
473
 
474
+ export const mapLayersSchema = Joi.array()
475
+ .items(Joi.string().valid('sssi'))
476
+ .single()
477
+ .description('The map layers to be displayed')
478
+
474
479
  export const exactFeaturesSchema = Joi.number()
475
480
  .empty('')
476
481
  .integer()
@@ -693,6 +698,7 @@ export const questionDetailsFullSchema = {
693
698
  maxChecksSchema,
694
699
  exactChecksSchema,
695
700
  countriesSchema,
701
+ mapLayersSchema,
696
702
  minFeaturesSchema,
697
703
  maxFeaturesSchema,
698
704
  exactFeaturesSchema,
@@ -0,0 +1,186 @@
1
+ import { ComponentType } from '~/src/components/enums.js'
2
+
3
+ /**
4
+ * @typedef {object} TranslationRow
5
+ * @property {string} name - the key name
6
+ * @property {string} [type] - type of text
7
+ * @property {string | undefined} englishContent - English text
8
+ * @property {string | undefined} welshContent - Welsh text
9
+ * @property {number} [pageNum] - page number
10
+ * @property {number} [questionNum] - question number
11
+ * @property {number} [itemNum] - list item number
12
+ * @property {string} [label] - label for field
13
+ */
14
+
15
+ /**
16
+ * @typedef {{ text: string, href?: string }} ErrorDetailsItem
17
+ * @typedef {Record<string, ErrorDetailsItem>} ErrorDetails
18
+ */
19
+
20
+ /**
21
+ * @template {object} [Schema=object]
22
+ * @typedef {object} ValidationFailure
23
+ * @property {ErrorDetails} formErrors - Formatted errors for error summary
24
+ * @property {Schema} formValues - Form POST payload from Hapi request
25
+ */
26
+
27
+ export const TranslationRowTypes = {
28
+ PageHeading: 'PageHeading',
29
+ PageGuidance: 'PageGuidance',
30
+ RepeatTitle: 'RepeatTitle',
31
+ QuestionText: 'QuestionText',
32
+ QuestionHint: 'QuestionHint',
33
+ ShortDescription: 'ShortDescription',
34
+ ErrorDescription: 'ErrorDescription',
35
+ ListItemText: 'ListItemText',
36
+ ListItemHint: 'ListItemHint',
37
+ InstructionText: 'InstructionText',
38
+ DeclarationBody: 'DeclarationBody',
39
+ PaymentDescription: 'PaymentDescription'
40
+ }
41
+
42
+ export const TEXTAREA_3_ROWS = 'textarea3'
43
+ export const TEXTAREA_5_ROWS = 'textarea5'
44
+ export const TEXTAREA_12_ROWS_WITH_MARKDOWN = 'textarea12markdown'
45
+ export const LIST_ITEM_HINT = 'listitemhint'
46
+ export const LIST_ITEM_WITH_HINT_FOLLOWING = 'listoptionwithhintfollowing'
47
+
48
+ /** @type {Record<string, { jsonPrefix: string, jsonSuffix: string, displayName: string, getLabel: (pageNum: number, questionNum?: number, itemNum?: number) => string }>} */
49
+ export const keyConfig = {
50
+ [TranslationRowTypes.QuestionText]: {
51
+ jsonPrefix: 'components',
52
+ jsonSuffix: 'title',
53
+ displayName: 'Question text',
54
+ getLabel: (pageNum, questionNum) =>
55
+ `Welsh question text - Page ${pageNum}, question ${questionNum}`
56
+ },
57
+ [TranslationRowTypes.QuestionHint]: {
58
+ jsonPrefix: 'components',
59
+ jsonSuffix: 'hint',
60
+ displayName: 'Hint',
61
+ getLabel: (pageNum, questionNum) =>
62
+ `Welsh hint - Page ${pageNum}, question ${questionNum}`
63
+ },
64
+ [TranslationRowTypes.ShortDescription]: {
65
+ jsonPrefix: 'components',
66
+ jsonSuffix: 'shortDescription',
67
+ displayName: 'Short description',
68
+ getLabel: (pageNum, questionNum) =>
69
+ `Welsh short description - Page ${pageNum}, question ${questionNum}`
70
+ },
71
+ [TranslationRowTypes.InstructionText]: {
72
+ jsonPrefix: 'components',
73
+ jsonSuffix: 'instructionText',
74
+ displayName: 'Instruction text',
75
+ getLabel: (pageNum, questionNum) =>
76
+ `Welsh instruction text - Page ${pageNum}, question ${questionNum}`
77
+ },
78
+ [TranslationRowTypes.DeclarationBody]: {
79
+ jsonPrefix: 'components',
80
+ jsonSuffix: 'content',
81
+ displayName: 'Declaration body',
82
+ getLabel: (pageNum, questionNum) =>
83
+ `Welsh declaration body - Page ${pageNum}, question ${questionNum}`
84
+ },
85
+ [TranslationRowTypes.PaymentDescription]: {
86
+ jsonPrefix: 'components',
87
+ jsonSuffix: 'paymentDescription',
88
+ displayName: 'Payment description',
89
+ getLabel: (pageNum, questionNum) =>
90
+ `Welsh payment description - Page ${pageNum}, question ${questionNum}`
91
+ },
92
+ [TranslationRowTypes.ListItemText]: {
93
+ jsonPrefix: 'listItems',
94
+ jsonSuffix: 'text',
95
+ displayName: 'Option',
96
+ getLabel: (pageNum, questionNum, itemNum) =>
97
+ `Welsh option ${itemNum} - Page ${pageNum}, question ${questionNum}`
98
+ },
99
+ [TranslationRowTypes.ListItemHint]: {
100
+ jsonPrefix: 'listItems',
101
+ jsonSuffix: 'hint',
102
+ displayName: 'Option hint',
103
+ getLabel: (pageNum, questionNum, itemNum) =>
104
+ `Welsh hint for option ${itemNum} - Page ${pageNum}, question ${questionNum}`
105
+ },
106
+ [TranslationRowTypes.PageGuidance]: {
107
+ jsonPrefix: 'components',
108
+ jsonSuffix: 'content',
109
+ displayName: 'Guidance text',
110
+ getLabel: (pageNum) => `Welsh guidance text (markdown) - page ${pageNum}`
111
+ },
112
+ [TranslationRowTypes.PageHeading]: {
113
+ jsonPrefix: 'pages',
114
+ jsonSuffix: 'title',
115
+ displayName: 'Page heading',
116
+ getLabel: (pageNum) => `Welsh page heading - page ${pageNum}`
117
+ },
118
+ [TranslationRowTypes.RepeatTitle]: {
119
+ jsonPrefix: 'pages',
120
+ jsonSuffix: 'repeatTitle',
121
+ displayName: 'Repeat name',
122
+ getLabel: (pageNum) => `Welsh repeat name - page ${pageNum}`
123
+ }
124
+ }
125
+
126
+ export const IGNORE_FIELDS = [
127
+ ComponentType.Details,
128
+ ComponentType.Html,
129
+ ComponentType.InsetText,
130
+ ComponentType.HiddenField
131
+ ]
132
+
133
+ /**
134
+ * Lookup a translation from a list of translation records.
135
+ * @param {string} key - The translation key to look up.
136
+ * @param {Record<string, string> | undefined} translations - The translation values to search.
137
+ */
138
+ export function lookupTranslation(key, translations) {
139
+ if (!translations) {
140
+ return ''
141
+ }
142
+ return translations[key] ?? ''
143
+ }
144
+
145
+ /**
146
+ * Resolve the underlying content for a translation row from an entity.
147
+ * Involves using the appropriate child properties if necessary.
148
+ * @param {string} keyType - The translation row type being resolved.
149
+ * @param {ComponentDef | Page | Item} entity - The entity that may contain the content to translate.
150
+ * @param {string} jsonSuffix - The property name to read from the entity.
151
+ */
152
+ export function drillDown(keyType, entity, jsonSuffix) {
153
+ /** @type {Partial<Record<string, (entity: ComponentDef | Page | Item) => string>>} */
154
+ const resolveData = {
155
+ [TranslationRowTypes.InstructionText]: () =>
156
+ // @ts-expect-error - dynamic property lookup
157
+ 'options' in entity ? entity.options?.instructionText : '',
158
+ [TranslationRowTypes.ListItemHint]: () =>
159
+ // @ts-expect-error - dynamic property lookup
160
+ 'hint' in entity ? entity.hint.text : '',
161
+ [TranslationRowTypes.DeclarationBody]: () =>
162
+ 'content' in entity ? entity.content : '',
163
+ [TranslationRowTypes.PaymentDescription]: () =>
164
+ // @ts-expect-error - dynamic property lookup
165
+ 'options' in entity ? entity.options?.description : ''
166
+ }
167
+ const func = resolveData[keyType]
168
+ if (func) {
169
+ return func(entity)
170
+ } else {
171
+ const entityWithDynamicProperties = /** @type {Record<string, unknown>} */ (
172
+ /** @type {unknown} */ (entity)
173
+ )
174
+ if (typeof entity === 'object') {
175
+ return jsonSuffix in entityWithDynamicProperties
176
+ ? /** @type {string} */ (entityWithDynamicProperties[jsonSuffix])
177
+ : ''
178
+ }
179
+ }
180
+ return entity
181
+ }
182
+
183
+ /**
184
+ * @import { ComponentDef } from '~/src/components/types.js'
185
+ * @import { Item, Page } from '~/src/form/form-definition/types.js'
186
+ */