@defra/forms-model 3.0.687 → 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 (45) 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-editor/translations/translations-config.js +168 -0
  7. package/dist/module/form/form-editor/translations/translations-config.js.map +1 -0
  8. package/dist/module/form/form-editor/translations/translations-overview.js +93 -0
  9. package/dist/module/form/form-editor/translations/translations-overview.js.map +1 -0
  10. package/dist/module/form/form-editor/translations/translations.js +214 -0
  11. package/dist/module/form/form-editor/translations/translations.js.map +1 -0
  12. package/dist/module/form/utils/list.js +23 -0
  13. package/dist/module/form/utils/list.js.map +1 -1
  14. package/dist/module/form/utils/numbering.js +43 -0
  15. package/dist/module/form/utils/numbering.js.map +1 -0
  16. package/dist/module/index.js +2 -0
  17. package/dist/module/index.js.map +1 -1
  18. package/dist/types/__stubs__/examples.d.ts +9 -0
  19. package/dist/types/__stubs__/examples.d.ts.map +1 -0
  20. package/dist/types/components/helpers.d.ts +2 -1
  21. package/dist/types/components/helpers.d.ts.map +1 -1
  22. package/dist/types/components/types.d.ts +1 -0
  23. package/dist/types/components/types.d.ts.map +1 -1
  24. package/dist/types/form/form-editor/translations/translations-config.d.ts +95 -0
  25. package/dist/types/form/form-editor/translations/translations-config.d.ts.map +1 -0
  26. package/dist/types/form/form-editor/translations/translations-overview.d.ts +14 -0
  27. package/dist/types/form/form-editor/translations/translations-overview.d.ts.map +1 -0
  28. package/dist/types/form/form-editor/translations/translations.d.ts +16 -0
  29. package/dist/types/form/form-editor/translations/translations.d.ts.map +1 -0
  30. package/dist/types/form/utils/list.d.ts +8 -1
  31. package/dist/types/form/utils/list.d.ts.map +1 -1
  32. package/dist/types/form/utils/numbering.d.ts +18 -0
  33. package/dist/types/form/utils/numbering.d.ts.map +1 -0
  34. package/dist/types/index.d.ts +2 -0
  35. package/dist/types/index.d.ts.map +1 -1
  36. package/package.json +1 -1
  37. package/src/__stubs__/examples.js +231 -0
  38. package/src/components/helpers.ts +14 -0
  39. package/src/components/types.ts +7 -0
  40. package/src/form/form-editor/translations/translations-config.js +186 -0
  41. package/src/form/form-editor/translations/translations-overview.js +137 -0
  42. package/src/form/form-editor/translations/translations.js +297 -0
  43. package/src/form/utils/list.ts +34 -1
  44. package/src/form/utils/numbering.js +47 -0
  45. package/src/index.ts +2 -0
@@ -1,4 +1,4 @@
1
- import { type ListComponentsDef } from '../../components/types.js';
1
+ import { type ComponentDef, type ListComponentsDef } from '../../components/types.js';
2
2
  import { type FormDefinition, type List } from '../../form/form-definition/types.js';
3
3
  /**
4
4
  * Given a SelectionComponent finds the associated list
@@ -7,4 +7,11 @@ import { type FormDefinition, type List } from '../../form/form-definition/types
7
7
  * @returns {List}
8
8
  */
9
9
  export declare function findDefinitionListFromComponent(selectionComponent: ListComponentsDef, definition: FormDefinition): List;
10
+ /**
11
+ * Finds the list in the component, if it exists. Handles a Yes/No list which doesn't link to the list in the normal way
12
+ * @param { ComponentDef | undefined } component
13
+ * @param {FormDefinition} definition
14
+ * @returns { List | undefined }
15
+ */
16
+ export declare function getListFromComponent(component: ComponentDef | undefined, definition: FormDefinition): List | undefined;
10
17
  //# sourceMappingURL=list.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"list.d.ts","sourceRoot":"","sources":["../../../../src/form/utils/list.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,iBAAiB,EAAE,MAAM,2BAA2B,CAAA;AAClE,OAAO,EACL,KAAK,cAAc,EACnB,KAAK,IAAI,EACV,MAAM,qCAAqC,CAAA;AAE5C;;;;;GAKG;AACH,wBAAgB,+BAA+B,CAC7C,kBAAkB,EAAE,iBAAiB,EACrC,UAAU,EAAE,cAAc,GACzB,IAAI,CASN"}
1
+ {"version":3,"file":"list.d.ts","sourceRoot":"","sources":["../../../../src/form/utils/list.ts"],"names":[],"mappings":"AAEA,OAAO,EACL,KAAK,YAAY,EACjB,KAAK,iBAAiB,EACvB,MAAM,2BAA2B,CAAA;AAElC,OAAO,EACL,KAAK,cAAc,EACnB,KAAK,IAAI,EACV,MAAM,qCAAqC,CAAA;AAE5C;;;;;GAKG;AACH,wBAAgB,+BAA+B,CAC7C,kBAAkB,EAAE,iBAAiB,EACrC,UAAU,EAAE,cAAc,GACzB,IAAI,CASN;AAED;;;;;GAKG;AACH,wBAAgB,oBAAoB,CAClC,SAAS,EAAE,YAAY,GAAG,SAAS,EACnC,UAAU,EAAE,cAAc,oBAiB3B"}
@@ -0,0 +1,18 @@
1
+ /**
2
+ * @param {FormDefinition} definition
3
+ * @param {string} pageId
4
+ */
5
+ export function getPageNum(definition: FormDefinition, pageId: string): number;
6
+ /**
7
+ * @param {FormDefinition} definition
8
+ * @param {string} pageId
9
+ */
10
+ export function getQuestionsOnPage(definition: FormDefinition, pageId: string): import("../../index.js").ComponentDef[];
11
+ /**
12
+ * @param {FormDefinition} definition
13
+ * @param {string} pageId
14
+ * @param {string} questionId
15
+ */
16
+ export function getQuestionNum(definition: FormDefinition, pageId: string, questionId: string): number;
17
+ import type { FormDefinition } from '../../form/form-definition/types.js';
18
+ //# sourceMappingURL=numbering.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"numbering.d.ts","sourceRoot":"","sources":["../../../../src/form/utils/numbering.js"],"names":[],"mappings":"AAOA;;;GAGG;AACH,uCAHW,cAAc,UACd,MAAM,UAQhB;AAED;;;GAGG;AACH,+CAHW,cAAc,UACd,MAAM,2CAKhB;AAED;;;;GAIG;AACH,2CAJW,cAAc,UACd,MAAM,cACN,MAAM,UAWhB;oCAGkC,qCAAqC"}
@@ -19,6 +19,8 @@ export * from './form/utils/index.js';
19
19
  export * from './form/form-editor/enums.js';
20
20
  export * from './form/form-editor/index.js';
21
21
  export * from './form/form-editor/preview/index.js';
22
+ export * from './form/form-editor/translations/translations-config.js';
23
+ export * from './form/form-editor/translations/translations.js';
22
24
  export * from './form/form-manager/types.js';
23
25
  export * from './form/form-manager/errors.js';
24
26
  export * from './manage/dead-letter-queues.js';
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,uBAAuB,CAAA;AACrC,cAAc,2BAA2B,CAAA;AACzC,cAAc,wBAAwB,CAAA;AACtC,cAAc,kCAAkC,CAAA;AAChD,cAAc,8BAA8B,CAAA;AAC5C,cAAc,+BAA+B,CAAA;AAC7C,cAAc,2BAA2B,CAAA;AACzC,cAAc,2BAA2B,CAAA;AACzC,cAAc,qCAAqC,CAAA;AACnD,cAAc,qCAAqC,CAAA;AACnD,cAAc,uCAAuC,CAAA;AACrD,cAAc,mCAAmC,CAAA;AACjD,cAAc,kCAAkC,CAAA;AAChD,cAAc,kCAAkC,CAAA;AAChD,cAAc,kCAAkC,CAAA;AAChD,cAAc,qCAAqC,CAAA;AACnD,cAAc,qCAAqC,CAAA;AACnD,cAAc,2BAA2B,CAAA;AACzC,cAAc,iCAAiC,CAAA;AAC/C,cAAc,iCAAiC,CAAA;AAC/C,cAAc,yCAAyC,CAAA;AACvD,cAAc,kCAAkC,CAAA;AAChD,cAAc,mCAAmC,CAAA;AACjD,cAAc,oCAAoC,CAAA;AAClD,cAAc,uBAAuB,CAAA;AACrC,cAAc,uBAAuB,CAAA;AACrC,cAAc,sBAAsB,CAAA;AACpC,cAAc,wBAAwB,CAAA;AACtC,cAAc,yBAAyB,CAAA;AACvC,cAAc,gCAAgC,CAAA;AAC9C,cAAc,gCAAgC,CAAA;AAC9C,cAAc,wCAAwC,CAAA;AAEtD,mBAAmB,uBAAuB,CAAA;AAC1C,mBAAmB,kCAAkC,CAAA;AACrD,mBAAmB,8BAA8B,CAAA;AACjD,mBAAmB,+BAA+B,CAAA;AAClD,mBAAmB,2BAA2B,CAAA;AAC9C,mBAAmB,2BAA2B,CAAA;AAC9C,mBAAmB,qCAAqC,CAAA;AACxD,mBAAmB,mCAAmC,CAAA;AACtD,mBAAmB,qCAAqC,CAAA;AACxD,mBAAmB,yCAAyC,CAAA;AAC5D,mBAAmB,iCAAiC,CAAA;AACpD,mBAAmB,wCAAwC,CAAA;AAC3D,mBAAmB,gCAAgC,CAAA;AACnD,mBAAmB,uBAAuB,CAAA"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,uBAAuB,CAAA;AACrC,cAAc,2BAA2B,CAAA;AACzC,cAAc,wBAAwB,CAAA;AACtC,cAAc,kCAAkC,CAAA;AAChD,cAAc,8BAA8B,CAAA;AAC5C,cAAc,+BAA+B,CAAA;AAC7C,cAAc,2BAA2B,CAAA;AACzC,cAAc,2BAA2B,CAAA;AACzC,cAAc,qCAAqC,CAAA;AACnD,cAAc,qCAAqC,CAAA;AACnD,cAAc,uCAAuC,CAAA;AACrD,cAAc,mCAAmC,CAAA;AACjD,cAAc,kCAAkC,CAAA;AAChD,cAAc,kCAAkC,CAAA;AAChD,cAAc,kCAAkC,CAAA;AAChD,cAAc,qCAAqC,CAAA;AACnD,cAAc,qCAAqC,CAAA;AACnD,cAAc,2BAA2B,CAAA;AACzC,cAAc,iCAAiC,CAAA;AAC/C,cAAc,iCAAiC,CAAA;AAC/C,cAAc,yCAAyC,CAAA;AACvD,cAAc,4DAA4D,CAAA;AAC1E,cAAc,qDAAqD,CAAA;AACnE,cAAc,kCAAkC,CAAA;AAChD,cAAc,mCAAmC,CAAA;AACjD,cAAc,oCAAoC,CAAA;AAClD,cAAc,uBAAuB,CAAA;AACrC,cAAc,uBAAuB,CAAA;AACrC,cAAc,sBAAsB,CAAA;AACpC,cAAc,wBAAwB,CAAA;AACtC,cAAc,yBAAyB,CAAA;AACvC,cAAc,gCAAgC,CAAA;AAC9C,cAAc,gCAAgC,CAAA;AAC9C,cAAc,wCAAwC,CAAA;AAEtD,mBAAmB,uBAAuB,CAAA;AAC1C,mBAAmB,kCAAkC,CAAA;AACrD,mBAAmB,8BAA8B,CAAA;AACjD,mBAAmB,+BAA+B,CAAA;AAClD,mBAAmB,2BAA2B,CAAA;AAC9C,mBAAmB,2BAA2B,CAAA;AAC9C,mBAAmB,qCAAqC,CAAA;AACxD,mBAAmB,mCAAmC,CAAA;AACtD,mBAAmB,qCAAqC,CAAA;AACxD,mBAAmB,yCAAyC,CAAA;AAC5D,mBAAmB,iCAAiC,CAAA;AACpD,mBAAmB,wCAAwC,CAAA;AAC3D,mBAAmB,gCAAgC,CAAA;AACnD,mBAAmB,uBAAuB,CAAA"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@defra/forms-model",
3
- "version": "3.0.687",
3
+ "version": "3.0.688",
4
4
  "description": "A hapi plugin providing the model for Defra forms",
5
5
  "homepage": "https://github.com/DEFRA/forms-designer/tree/main/model#readme",
6
6
  "types": "dist/types/index.d.ts",
@@ -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)
@@ -443,6 +443,13 @@ export type SelectionComponentsDef =
443
443
  | SelectFieldComponent
444
444
  | YesNoFieldComponent
445
445
 
446
+ // Components that have instruction fields
447
+ export type ComponentsDefWithInstructions =
448
+ | EastingNorthingFieldComponent
449
+ | OsGridRefFieldComponent
450
+ | NationalGridFieldNumberFieldComponent
451
+ | LatLongFieldComponent
452
+
446
453
  // Components that have condition support
447
454
  export type ConditionalComponentsDef = Exclude<
448
455
  ComponentDef,
@@ -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
+ */
@@ -0,0 +1,137 @@
1
+ import { lookupTranslation } from '~/src/form/form-editor/translations/translations-config.js'
2
+
3
+ const formNameKey = 'form.title'
4
+ const contactEmailAddressKey = 'form.contact.email.address'
5
+ const contactEmailResponseKey = 'form.contact.email.responseTime'
6
+ const contactOnlineUrlKey = 'form.contact.online.url'
7
+ const contactOnlineTextKey = 'form.contact.online.text'
8
+ const contactPhoneKey = 'form.contact.phone'
9
+ const submissionGuidanceKey = 'form.submissionGuidance'
10
+ const privacyNoticeUrlKey = 'form.privacyNoticeUrl'
11
+ const privacyNoticeTextKey = 'form.privacyNoticeText'
12
+
13
+ /**
14
+ * Determine a field value, either from the user-entered payload (validation) or from a translation lookup
15
+ * @param {string} key
16
+ * @param {Record<string, string>} translations
17
+ * @param {ValidationFailure<any>} [validation]
18
+ * @returns { string | undefined }
19
+ */
20
+ function getTranslation(key, translations, validation) {
21
+ return validation?.formValues[key] ?? lookupTranslation(key, translations)
22
+ }
23
+
24
+ /**
25
+ * Build all translation rows in the form overview section (metatdata values).
26
+ * @param {FormMetadata} metadata
27
+ * @param {FormDefinition} definition
28
+ * @param {Record<string, string>} translations
29
+ * @param {ValidationFailure<any>} [validation]
30
+ * @returns {TranslationRow[]}
31
+ */
32
+ export function buildOverviewSection(
33
+ metadata,
34
+ definition,
35
+ translations,
36
+ validation
37
+ ) {
38
+ /** @type {TranslationRow[]} */
39
+ const rows = []
40
+
41
+ rows.push(
42
+ {
43
+ name: formNameKey,
44
+ englishContent: definition.name,
45
+ welshContent: getTranslation(formNameKey, translations, validation),
46
+ label: 'Welsh form name'
47
+ },
48
+ {
49
+ name: contactEmailAddressKey,
50
+ englishContent: metadata.contact?.email?.address ?? 'Not set',
51
+ welshContent: getTranslation(
52
+ contactEmailAddressKey,
53
+ translations,
54
+ validation
55
+ ),
56
+ label: 'Welsh support email address'
57
+ },
58
+ {
59
+ name: contactEmailResponseKey,
60
+ englishContent: metadata.contact?.email?.responseTime ?? '--',
61
+ welshContent: getTranslation(
62
+ contactEmailResponseKey,
63
+ translations,
64
+ validation
65
+ ),
66
+ label: 'Welsh response time message'
67
+ },
68
+ {
69
+ name: contactOnlineUrlKey,
70
+ englishContent: metadata.contact?.online?.url ?? 'Not set',
71
+ welshContent: getTranslation(
72
+ contactOnlineUrlKey,
73
+ translations,
74
+ validation
75
+ ),
76
+ label: 'Welsh contact link URL'
77
+ },
78
+ {
79
+ name: contactOnlineTextKey,
80
+ englishContent: metadata.contact?.online?.text ?? '--',
81
+ welshContent: getTranslation(
82
+ contactOnlineTextKey,
83
+ translations,
84
+ validation
85
+ ),
86
+ label: 'Welsh text for the contact link'
87
+ },
88
+ {
89
+ name: contactPhoneKey,
90
+ englishContent: metadata.contact?.phone ?? 'Not set',
91
+ welshContent: getTranslation(contactPhoneKey, translations, validation),
92
+ label: 'Welsh phone number and opening times'
93
+ },
94
+ {
95
+ name: submissionGuidanceKey,
96
+ englishContent: metadata.submissionGuidance ?? 'Not set',
97
+ welshContent: getTranslation(
98
+ submissionGuidanceKey,
99
+ translations,
100
+ validation
101
+ ),
102
+ label: 'Welsh information about what happens next'
103
+ }
104
+ )
105
+
106
+ if (metadata.privacyNoticeType === 'link') {
107
+ rows.push({
108
+ name: privacyNoticeUrlKey,
109
+ englishContent: metadata.privacyNoticeUrl ?? 'Not set',
110
+ welshContent: getTranslation(
111
+ privacyNoticeUrlKey,
112
+ translations,
113
+ validation
114
+ ),
115
+ label: 'Welsh privacy notice URL'
116
+ })
117
+ } else {
118
+ rows.push({
119
+ name: privacyNoticeTextKey,
120
+ englishContent: metadata.privacyNoticeText ?? 'Not set',
121
+ welshContent: getTranslation(
122
+ privacyNoticeTextKey,
123
+ translations,
124
+ validation
125
+ ),
126
+ label: 'Welsh privacy notice text'
127
+ })
128
+ }
129
+
130
+ return /** @type {TranslationRow[]} */ (rows)
131
+ }
132
+
133
+ /**
134
+ * @import { FormMetadata } from '~/src/form/form-metadata/types.js'
135
+ * @import { FormDefinition } from '~/src/form/form-definition/types.js'
136
+ * @import { TranslationRow, ValidationFailure} from '~/src/form/form-editor/translations/translations-config.js'
137
+ */