@defra/forms-engine-plugin 5.0.0-alpha.3 → 5.0.0-alpha.31

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 (406) hide show
  1. package/.public/37c964ca545c240f010a.mjs +2 -0
  2. package/.public/37c964ca545c240f010a.mjs.LICENSE.txt +4 -0
  3. package/.public/assets-manifest.json +3 -0
  4. package/.public/javascripts/application.min.js +1 -1
  5. package/.public/javascripts/application.min.js.LICENSE.txt +7 -0
  6. package/.public/javascripts/application.min.js.map +1 -1
  7. package/.public/javascripts/shared.min.js +1 -1
  8. package/.public/javascripts/shared.min.js.LICENSE.txt +9 -0
  9. package/.public/javascripts/shared.min.js.map +1 -1
  10. package/.public/javascripts/vendor/@defra.min.js +1 -1
  11. package/.public/javascripts/vendor/@defra.min.js.map +1 -1
  12. package/.public/javascripts/vendor/maplibre-gl.min.js +1 -1
  13. package/.public/javascripts/vendor/maplibre-gl.min.js.LICENSE.txt +3 -3
  14. package/.public/javascripts/vendor/maplibre-gl.min.js.map +1 -1
  15. package/.public/stylesheets/application.min.css +1 -1
  16. package/.public/stylesheets/application.min.css.map +1 -1
  17. package/.server/client/javascripts/file-upload.js +6 -3
  18. package/.server/client/javascripts/file-upload.js.map +1 -1
  19. package/.server/client/javascripts/geospatial-map.d.ts +159 -5
  20. package/.server/client/javascripts/geospatial-map.js +161 -81
  21. package/.server/client/javascripts/geospatial-map.js.map +1 -1
  22. package/.server/client/javascripts/geospatial-map.test.js +152 -0
  23. package/.server/client/javascripts/geospatial-map.test.js.map +1 -0
  24. package/.server/client/javascripts/govuk.js +2 -1
  25. package/.server/client/javascripts/govuk.js.map +1 -1
  26. package/.server/client/javascripts/location-map.d.ts +44 -0
  27. package/.server/client/javascripts/location-map.js +55 -6
  28. package/.server/client/javascripts/location-map.js.map +1 -1
  29. package/.server/client/javascripts/map-constants.d.ts +3 -0
  30. package/.server/client/javascripts/map-constants.js +4 -0
  31. package/.server/client/javascripts/map-constants.js.map +1 -0
  32. package/.server/client/javascripts/map.d.ts +17 -4
  33. package/.server/client/javascripts/map.js +59 -6
  34. package/.server/client/javascripts/map.js.map +1 -1
  35. package/.server/client/javascripts/maps.d.ts +5 -0
  36. package/.server/client/javascripts/maps.js +6 -0
  37. package/.server/client/javascripts/maps.js.map +1 -0
  38. package/.server/client/javascripts/shared.d.ts +1 -4
  39. package/.server/client/javascripts/shared.js +1 -4
  40. package/.server/client/javascripts/shared.js.map +1 -1
  41. package/.server/client/javascripts/sssi-dataset.d.ts +18 -20
  42. package/.server/client/javascripts/sssi-dataset.js +37 -39
  43. package/.server/client/javascripts/sssi-dataset.js.map +1 -1
  44. package/.server/client/stylesheets/maps.scss +4 -0
  45. package/.server/client/stylesheets/shared.scss +1 -5
  46. package/.server/server/constants.d.ts +2 -0
  47. package/.server/server/constants.js +2 -0
  48. package/.server/server/constants.js.map +1 -1
  49. package/.server/server/forms/components.json +8 -0
  50. package/.server/server/forms/register-as-a-unicorn-breeder.yaml +4 -0
  51. package/.server/server/plugins/engine/__stubs__/definitions.d.ts +29 -0
  52. package/.server/server/plugins/engine/__stubs__/definitions.js +125 -0
  53. package/.server/server/plugins/engine/__stubs__/definitions.js.map +1 -0
  54. package/.server/server/plugins/engine/components/AutocompleteField.d.ts +8 -3
  55. package/.server/server/plugins/engine/components/AutocompleteField.js +17 -0
  56. package/.server/server/plugins/engine/components/AutocompleteField.js.map +1 -1
  57. package/.server/server/plugins/engine/components/CheckboxesField.d.ts +23 -2
  58. package/.server/server/plugins/engine/components/CheckboxesField.js +30 -5
  59. package/.server/server/plugins/engine/components/CheckboxesField.js.map +1 -1
  60. package/.server/server/plugins/engine/components/ComponentBase.d.ts +2 -1
  61. package/.server/server/plugins/engine/components/ComponentBase.js +2 -0
  62. package/.server/server/plugins/engine/components/ComponentBase.js.map +1 -1
  63. package/.server/server/plugins/engine/components/ComponentCollection.js +12 -7
  64. package/.server/server/plugins/engine/components/ComponentCollection.js.map +1 -1
  65. package/.server/server/plugins/engine/components/DatePartsField.d.ts +6 -3
  66. package/.server/server/plugins/engine/components/DatePartsField.js +12 -7
  67. package/.server/server/plugins/engine/components/DatePartsField.js.map +1 -1
  68. package/.server/server/plugins/engine/components/DeclarationField.d.ts +6 -3
  69. package/.server/server/plugins/engine/components/DeclarationField.js +6 -4
  70. package/.server/server/plugins/engine/components/DeclarationField.js.map +1 -1
  71. package/.server/server/plugins/engine/components/Details.d.ts +1 -0
  72. package/.server/server/plugins/engine/components/EastingNorthingField.d.ts +33 -2
  73. package/.server/server/plugins/engine/components/EastingNorthingField.js +110 -54
  74. package/.server/server/plugins/engine/components/EastingNorthingField.js.map +1 -1
  75. package/.server/server/plugins/engine/components/EmailAddressField.d.ts +4 -1
  76. package/.server/server/plugins/engine/components/EmailAddressField.js +2 -1
  77. package/.server/server/plugins/engine/components/EmailAddressField.js.map +1 -1
  78. package/.server/server/plugins/engine/components/FileUploadField.d.ts +23 -2
  79. package/.server/server/plugins/engine/components/FileUploadField.js +35 -3
  80. package/.server/server/plugins/engine/components/FileUploadField.js.map +1 -1
  81. package/.server/server/plugins/engine/components/FormComponent.d.ts +5 -1
  82. package/.server/server/plugins/engine/components/FormComponent.js +5 -2
  83. package/.server/server/plugins/engine/components/FormComponent.js.map +1 -1
  84. package/.server/server/plugins/engine/components/GeospatialField.d.ts +23 -0
  85. package/.server/server/plugins/engine/components/GeospatialField.js +33 -9
  86. package/.server/server/plugins/engine/components/GeospatialField.js.map +1 -1
  87. package/.server/server/plugins/engine/components/Html.d.ts +1 -0
  88. package/.server/server/plugins/engine/components/InsetText.d.ts +1 -0
  89. package/.server/server/plugins/engine/components/LatLongField.d.ts +32 -2
  90. package/.server/server/plugins/engine/components/LatLongField.js +85 -48
  91. package/.server/server/plugins/engine/components/LatLongField.js.map +1 -1
  92. package/.server/server/plugins/engine/components/List.d.ts +1 -0
  93. package/.server/server/plugins/engine/components/ListFormComponent.d.ts +3 -2
  94. package/.server/server/plugins/engine/components/ListFormComponent.js.map +1 -1
  95. package/.server/server/plugins/engine/components/LocationFieldBase.d.ts +8 -2
  96. package/.server/server/plugins/engine/components/LocationFieldBase.js +31 -7
  97. package/.server/server/plugins/engine/components/LocationFieldBase.js.map +1 -1
  98. package/.server/server/plugins/engine/components/LocationFieldHelpers.d.ts +12 -3
  99. package/.server/server/plugins/engine/components/LocationFieldHelpers.js +21 -13
  100. package/.server/server/plugins/engine/components/LocationFieldHelpers.js.map +1 -1
  101. package/.server/server/plugins/engine/components/Markdown.d.ts +1 -0
  102. package/.server/server/plugins/engine/components/Markdown.js +1 -1
  103. package/.server/server/plugins/engine/components/Markdown.js.map +1 -1
  104. package/.server/server/plugins/engine/components/MonthYearField.d.ts +5 -2
  105. package/.server/server/plugins/engine/components/MonthYearField.js +13 -9
  106. package/.server/server/plugins/engine/components/MonthYearField.js.map +1 -1
  107. package/.server/server/plugins/engine/components/MultilineTextField.d.ts +1 -0
  108. package/.server/server/plugins/engine/components/NationalGridFieldNumberField.d.ts +2 -1
  109. package/.server/server/plugins/engine/components/NationalGridFieldNumberField.js +4 -3
  110. package/.server/server/plugins/engine/components/NationalGridFieldNumberField.js.map +1 -1
  111. package/.server/server/plugins/engine/components/NotificationBanner.d.ts +1 -0
  112. package/.server/server/plugins/engine/components/NumberField.d.ts +5 -0
  113. package/.server/server/plugins/engine/components/NumberField.js +22 -0
  114. package/.server/server/plugins/engine/components/NumberField.js.map +1 -1
  115. package/.server/server/plugins/engine/components/OsGridRefField.d.ts +103 -1
  116. package/.server/server/plugins/engine/components/OsGridRefField.js +13 -3
  117. package/.server/server/plugins/engine/components/OsGridRefField.js.map +1 -1
  118. package/.server/server/plugins/engine/components/PaymentField.d.ts +2 -0
  119. package/.server/server/plugins/engine/components/PaymentField.js +9 -5
  120. package/.server/server/plugins/engine/components/PaymentField.js.map +1 -1
  121. package/.server/server/plugins/engine/components/SelectField.d.ts +1 -0
  122. package/.server/server/plugins/engine/components/SelectionControlField.d.ts +1 -0
  123. package/.server/server/plugins/engine/components/TelephoneNumberField.d.ts +1 -0
  124. package/.server/server/plugins/engine/components/TrackingField.d.ts +21 -0
  125. package/.server/server/plugins/engine/components/TrackingField.js +46 -0
  126. package/.server/server/plugins/engine/components/TrackingField.js.map +1 -0
  127. package/.server/server/plugins/engine/components/UkAddressField.d.ts +1 -0
  128. package/.server/server/plugins/engine/components/UkAddressField.js +23 -8
  129. package/.server/server/plugins/engine/components/UkAddressField.js.map +1 -1
  130. package/.server/server/plugins/engine/components/YesNoField.d.ts +3 -2
  131. package/.server/server/plugins/engine/components/YesNoField.js +22 -8
  132. package/.server/server/plugins/engine/components/YesNoField.js.map +1 -1
  133. package/.server/server/plugins/engine/components/helpers/components.d.ts +1 -1
  134. package/.server/server/plugins/engine/components/helpers/components.js +6 -3
  135. package/.server/server/plugins/engine/components/helpers/components.js.map +1 -1
  136. package/.server/server/plugins/engine/components/helpers/geospatial.d.ts +8 -1
  137. package/.server/server/plugins/engine/components/helpers/geospatial.js +58 -17
  138. package/.server/server/plugins/engine/components/helpers/geospatial.js.map +1 -1
  139. package/.server/server/plugins/engine/components/helpers/index.d.ts +21 -1
  140. package/.server/server/plugins/engine/components/helpers/index.js +45 -1
  141. package/.server/server/plugins/engine/components/helpers/index.js.map +1 -1
  142. package/.server/server/plugins/engine/components/index.d.ts +1 -0
  143. package/.server/server/plugins/engine/components/index.js +1 -0
  144. package/.server/server/plugins/engine/components/index.js.map +1 -1
  145. package/.server/server/plugins/engine/components/types.d.ts +1 -0
  146. package/.server/server/plugins/engine/components/types.js.map +1 -1
  147. package/.server/server/plugins/engine/errors.d.ts +49 -0
  148. package/.server/server/plugins/engine/errors.js +75 -0
  149. package/.server/server/plugins/engine/errors.js.map +1 -0
  150. package/.server/server/plugins/engine/form-availability.d.ts +3 -2
  151. package/.server/server/plugins/engine/form-availability.js +3 -2
  152. package/.server/server/plugins/engine/form-availability.js.map +1 -1
  153. package/.server/server/plugins/engine/form-context.js +2 -2
  154. package/.server/server/plugins/engine/form-context.js.map +1 -1
  155. package/.server/server/plugins/engine/helpers.d.ts +23 -2
  156. package/.server/server/plugins/engine/helpers.js +44 -5
  157. package/.server/server/plugins/engine/helpers.js.map +1 -1
  158. package/.server/server/plugins/engine/i18n/buildValidationMessages.d.ts +4 -1
  159. package/.server/server/plugins/engine/i18n/buildValidationMessages.js +31 -7
  160. package/.server/server/plugins/engine/i18n/buildValidationMessages.js.map +1 -1
  161. package/.server/server/plugins/engine/i18n/createFormTranslator.d.ts +4 -4
  162. package/.server/server/plugins/engine/i18n/createFormTranslator.js +27 -5
  163. package/.server/server/plugins/engine/i18n/createFormTranslator.js.map +1 -1
  164. package/.server/server/plugins/engine/i18n/extractBaseTranslations.d.ts +1 -1
  165. package/.server/server/plugins/engine/i18n/extractBaseTranslations.js +9 -0
  166. package/.server/server/plugins/engine/i18n/extractBaseTranslations.js.map +1 -1
  167. package/.server/server/plugins/engine/i18n/form.d.ts +13 -0
  168. package/.server/server/plugins/engine/i18n/form.js +35 -0
  169. package/.server/server/plugins/engine/i18n/form.js.map +1 -0
  170. package/.server/server/plugins/engine/i18n/languages.d.ts +10 -0
  171. package/.server/server/plugins/engine/i18n/languages.js +19 -0
  172. package/.server/server/plugins/engine/i18n/languages.js.map +1 -0
  173. package/.server/server/plugins/engine/i18n/translations/cy.json +176 -7
  174. package/.server/server/plugins/engine/i18n/translations/en-GB.json +110 -6
  175. package/.server/server/plugins/engine/i18n/types.d.ts +2 -0
  176. package/.server/server/plugins/engine/i18n/types.js.map +1 -1
  177. package/.server/server/plugins/engine/index.d.ts +4 -0
  178. package/.server/server/plugins/engine/index.js +4 -0
  179. package/.server/server/plugins/engine/index.js.map +1 -1
  180. package/.server/server/plugins/engine/models/FormModel.d.ts +0 -4
  181. package/.server/server/plugins/engine/models/FormModel.js +35 -23
  182. package/.server/server/plugins/engine/models/FormModel.js.map +1 -1
  183. package/.server/server/plugins/engine/models/SummaryViewModel.js +9 -4
  184. package/.server/server/plugins/engine/models/SummaryViewModel.js.map +1 -1
  185. package/.server/server/plugins/engine/models/types.d.ts +28 -1
  186. package/.server/server/plugins/engine/models/types.js.map +1 -1
  187. package/.server/server/plugins/engine/models/unavailable-view-model.d.ts +11 -2
  188. package/.server/server/plugins/engine/models/unavailable-view-model.js +23 -4
  189. package/.server/server/plugins/engine/models/unavailable-view-model.js.map +1 -1
  190. package/.server/server/plugins/engine/options.js +2 -1
  191. package/.server/server/plugins/engine/options.js.map +1 -1
  192. package/.server/server/plugins/engine/outputFormatters/adapter/v1.js +14 -6
  193. package/.server/server/plugins/engine/outputFormatters/adapter/v1.js.map +1 -1
  194. package/.server/server/plugins/engine/outputFormatters/human/v1.js +2 -1
  195. package/.server/server/plugins/engine/outputFormatters/human/v1.js.map +1 -1
  196. package/.server/server/plugins/engine/outputFormatters/machine/v2.d.ts +2 -0
  197. package/.server/server/plugins/engine/outputFormatters/machine/v2.js +2 -0
  198. package/.server/server/plugins/engine/outputFormatters/machine/v2.js.map +1 -1
  199. package/.server/server/plugins/engine/pageControllers/FileUploadPageController.d.ts +7 -1
  200. package/.server/server/plugins/engine/pageControllers/FileUploadPageController.js +56 -10
  201. package/.server/server/plugins/engine/pageControllers/FileUploadPageController.js.map +1 -1
  202. package/.server/server/plugins/engine/pageControllers/QuestionPageController.d.ts +1 -1
  203. package/.server/server/plugins/engine/pageControllers/QuestionPageController.js +2 -2
  204. package/.server/server/plugins/engine/pageControllers/QuestionPageController.js.map +1 -1
  205. package/.server/server/plugins/engine/pageControllers/RepeatPageController.js +3 -4
  206. package/.server/server/plugins/engine/pageControllers/RepeatPageController.js.map +1 -1
  207. package/.server/server/plugins/engine/pageControllers/SummaryPageController.js +10 -4
  208. package/.server/server/plugins/engine/pageControllers/SummaryPageController.js.map +1 -1
  209. package/.server/server/plugins/engine/pageControllers/helpers/pages.js +2 -1
  210. package/.server/server/plugins/engine/pageControllers/helpers/pages.js.map +1 -1
  211. package/.server/server/plugins/engine/pageControllers/helpers/submission.d.ts +35 -2
  212. package/.server/server/plugins/engine/pageControllers/helpers/submission.js +100 -3
  213. package/.server/server/plugins/engine/pageControllers/helpers/submission.js.map +1 -1
  214. package/.server/server/plugins/engine/pageControllers/validationOptions.d.ts +1 -1
  215. package/.server/server/plugins/engine/pageControllers/validationOptions.js +24 -3
  216. package/.server/server/plugins/engine/pageControllers/validationOptions.js.map +1 -1
  217. package/.server/server/plugins/engine/plugin.js +3 -0
  218. package/.server/server/plugins/engine/plugin.js.map +1 -1
  219. package/.server/server/plugins/engine/referenceNumbers.d.ts +1 -0
  220. package/.server/server/plugins/engine/referenceNumbers.js +1 -0
  221. package/.server/server/plugins/engine/referenceNumbers.js.map +1 -1
  222. package/.server/server/plugins/engine/routes/index.js +9 -4
  223. package/.server/server/plugins/engine/routes/index.js.map +1 -1
  224. package/.server/server/plugins/engine/types/enums.d.ts +3 -3
  225. package/.server/server/plugins/engine/types/enums.js +5 -4
  226. package/.server/server/plugins/engine/types/enums.js.map +1 -1
  227. package/.server/server/plugins/engine/types/schema.js +15 -4
  228. package/.server/server/plugins/engine/types/schema.js.map +1 -1
  229. package/.server/server/plugins/engine/types.d.ts +24 -2
  230. package/.server/server/plugins/engine/types.js.map +1 -1
  231. package/.server/server/plugins/engine/unavailable-response.js +5 -2
  232. package/.server/server/plugins/engine/unavailable-response.js.map +1 -1
  233. package/.server/server/plugins/engine/views/components/_location-field-base.html +4 -2
  234. package/.server/server/plugins/engine/views/components/fileuploadfield.html +1 -1
  235. package/.server/server/plugins/engine/views/components/geospatialfield.html +1 -0
  236. package/.server/server/plugins/engine/views/components/osgridreffield.html +4 -1
  237. package/.server/server/plugins/engine/views/components/trackingfield.html +3 -0
  238. package/.server/server/plugins/engine/views/components/ukaddressfield.html +1 -1
  239. package/.server/server/plugins/engine/views/file-upload.html +1 -1
  240. package/.server/server/plugins/engine/views/summary.html +1 -0
  241. package/.server/server/plugins/engine/views/unavailable.html +14 -11
  242. package/.server/server/plugins/map/routes/index.d.ts +4 -0
  243. package/.server/server/plugins/map/routes/index.js +12 -4
  244. package/.server/server/plugins/map/routes/index.js.map +1 -1
  245. package/.server/server/plugins/map/routes/index.test.js +7 -0
  246. package/.server/server/plugins/map/routes/index.test.js.map +1 -0
  247. package/.server/server/plugins/map/routes/vts/README.md +38 -0
  248. package/.server/server/plugins/map/routes/vts/countries-5dp.geojson +4 -0
  249. package/.server/server/plugins/map/routes/vts/countries.geojson +10 -39283
  250. package/.server/server/plugins/map/types.d.ts +24 -2
  251. package/.server/server/plugins/map/types.js +9 -2
  252. package/.server/server/plugins/map/types.js.map +1 -1
  253. package/.server/server/plugins/nunjucks/enviroment.test.js +11 -4
  254. package/.server/server/plugins/nunjucks/enviroment.test.js.map +1 -1
  255. package/.server/server/plugins/nunjucks/filters/applyUrlParam.d.ts +9 -0
  256. package/.server/server/plugins/nunjucks/filters/applyUrlParam.js +17 -0
  257. package/.server/server/plugins/nunjucks/filters/applyUrlParam.js.map +1 -0
  258. package/.server/server/plugins/nunjucks/filters/applyUrlParam.test.js +17 -0
  259. package/.server/server/plugins/nunjucks/filters/applyUrlParam.test.js.map +1 -0
  260. package/.server/server/plugins/nunjucks/filters/field.d.ts +1 -1
  261. package/.server/server/plugins/nunjucks/filters/index.d.ts +1 -0
  262. package/.server/server/plugins/nunjucks/filters/index.js +1 -0
  263. package/.server/server/plugins/nunjucks/filters/index.js.map +1 -1
  264. package/.server/server/plugins/payment/helper.d.ts +11 -1
  265. package/.server/server/plugins/payment/helper.js +18 -2
  266. package/.server/server/plugins/payment/helper.js.map +1 -1
  267. package/.server/server/plugins/payment/helper.test.js +2 -1
  268. package/.server/server/plugins/payment/helper.test.js.map +1 -1
  269. package/.server/server/plugins/payment/service.d.ts +2 -1
  270. package/.server/server/plugins/payment/service.js +5 -2
  271. package/.server/server/plugins/payment/service.js.map +1 -1
  272. package/.server/server/plugins/payment/service.test.js +7 -3
  273. package/.server/server/plugins/payment/service.test.js.map +1 -1
  274. package/.server/server/plugins/payment/types.d.ts +4 -0
  275. package/.server/server/plugins/payment/types.js +1 -0
  276. package/.server/server/plugins/payment/types.js.map +1 -1
  277. package/.server/server/plugins/postcode-lookup/models/index.d.ts +249 -3
  278. package/.server/server/plugins/postcode-lookup/models/index.js +103 -36
  279. package/.server/server/plugins/postcode-lookup/models/index.js.map +1 -1
  280. package/.server/server/plugins/postcode-lookup/routes/index.d.ts +15 -1
  281. package/.server/server/plugins/postcode-lookup/routes/index.js +64 -18
  282. package/.server/server/plugins/postcode-lookup/routes/index.js.map +1 -1
  283. package/.server/server/plugins/postcode-lookup/routes/index.test.js +84 -0
  284. package/.server/server/plugins/postcode-lookup/routes/index.test.js.map +1 -0
  285. package/.server/server/plugins/postcode-lookup/types.d.ts +12 -0
  286. package/.server/server/plugins/postcode-lookup/types.js +6 -1
  287. package/.server/server/plugins/postcode-lookup/types.js.map +1 -1
  288. package/.server/server/plugins/postcode-lookup/views/postcode-lookup-details.html +3 -3
  289. package/.server/server/utils/type-utils.d.ts +1 -2
  290. package/.server/server/utils/type-utils.js +0 -3
  291. package/.server/server/utils/type-utils.js.map +1 -1
  292. package/.server/server/utils/utils.d.ts +6 -0
  293. package/.server/server/utils/utils.js +18 -1
  294. package/.server/server/utils/utils.js.map +1 -1
  295. package/.server/server/utils/utils.test.js +21 -1
  296. package/.server/server/utils/utils.test.js.map +1 -1
  297. package/.server/typings/geodesy/index.d.js +2 -0
  298. package/.server/typings/geodesy/index.d.js.map +1 -0
  299. package/.server/typings/hapi/index.d.js.map +1 -1
  300. package/package.json +24 -9
  301. package/src/client/javascripts/file-upload.js +11 -3
  302. package/src/client/javascripts/geospatial-map.js +181 -86
  303. package/src/client/javascripts/govuk.js +2 -0
  304. package/src/client/javascripts/location-map.js +62 -6
  305. package/src/client/javascripts/map-constants.js +3 -0
  306. package/src/client/javascripts/map.js +66 -9
  307. package/src/client/javascripts/maps.js +5 -0
  308. package/src/client/javascripts/shared.js +1 -4
  309. package/src/client/javascripts/sssi-dataset.js +39 -41
  310. package/src/client/stylesheets/maps.scss +4 -0
  311. package/src/client/stylesheets/shared.scss +1 -5
  312. package/src/server/constants.js +2 -0
  313. package/src/server/forms/components.json +8 -0
  314. package/src/server/forms/register-as-a-unicorn-breeder.yaml +4 -0
  315. package/src/server/plugins/engine/__stubs__/definitions.ts +155 -0
  316. package/src/server/plugins/engine/components/AutocompleteField.ts +20 -0
  317. package/src/server/plugins/engine/components/CheckboxesField.ts +41 -8
  318. package/src/server/plugins/engine/components/ComponentBase.ts +6 -4
  319. package/src/server/plugins/engine/components/ComponentCollection.ts +17 -15
  320. package/src/server/plugins/engine/components/DatePartsField.ts +20 -9
  321. package/src/server/plugins/engine/components/DeclarationField.ts +10 -10
  322. package/src/server/plugins/engine/components/EastingNorthingField.ts +185 -89
  323. package/src/server/plugins/engine/components/EmailAddressField.ts +2 -1
  324. package/src/server/plugins/engine/components/FileUploadField.ts +46 -5
  325. package/src/server/plugins/engine/components/FormComponent.ts +6 -5
  326. package/src/server/plugins/engine/components/GeospatialField.ts +47 -9
  327. package/src/server/plugins/engine/components/LatLongField.ts +128 -58
  328. package/src/server/plugins/engine/components/ListFormComponent.ts +4 -4
  329. package/src/server/plugins/engine/components/LocationFieldBase.ts +41 -8
  330. package/src/server/plugins/engine/components/LocationFieldHelpers.ts +28 -14
  331. package/src/server/plugins/engine/components/Markdown.ts +2 -3
  332. package/src/server/plugins/engine/components/MonthYearField.ts +20 -15
  333. package/src/server/plugins/engine/components/NationalGridFieldNumberField.ts +9 -3
  334. package/src/server/plugins/engine/components/NumberField.ts +24 -0
  335. package/src/server/plugins/engine/components/OsGridRefField.ts +22 -3
  336. package/src/server/plugins/engine/components/PaymentField.ts +9 -4
  337. package/src/server/plugins/engine/components/TrackingField.ts +64 -0
  338. package/src/server/plugins/engine/components/UkAddressField.ts +30 -7
  339. package/src/server/plugins/engine/components/YesNoField.ts +27 -9
  340. package/src/server/plugins/engine/components/helpers/components.ts +8 -3
  341. package/src/server/plugins/engine/components/helpers/geospatial.ts +87 -24
  342. package/src/server/plugins/engine/components/helpers/index.ts +64 -1
  343. package/src/server/plugins/engine/components/index.ts +1 -0
  344. package/src/server/plugins/engine/components/types.ts +1 -0
  345. package/src/server/plugins/engine/errors.ts +78 -0
  346. package/src/server/plugins/engine/form-availability.ts +8 -2
  347. package/src/server/plugins/engine/form-context.ts +4 -2
  348. package/src/server/plugins/engine/helpers.ts +52 -5
  349. package/src/server/plugins/engine/i18n/buildValidationMessages.ts +16 -8
  350. package/src/server/plugins/engine/i18n/createFormTranslator.ts +43 -12
  351. package/src/server/plugins/engine/i18n/extractBaseTranslations.ts +7 -1
  352. package/src/server/plugins/engine/i18n/form.ts +54 -0
  353. package/src/server/plugins/engine/i18n/languages.ts +19 -0
  354. package/src/server/plugins/engine/i18n/translations/cy.json +176 -7
  355. package/src/server/plugins/engine/i18n/translations/en-GB.json +110 -6
  356. package/src/server/plugins/engine/i18n/types.ts +2 -0
  357. package/src/server/plugins/engine/index.ts +7 -0
  358. package/src/server/plugins/engine/models/FormModel.ts +46 -23
  359. package/src/server/plugins/engine/models/SummaryViewModel.ts +6 -5
  360. package/src/server/plugins/engine/models/types.ts +31 -0
  361. package/src/server/plugins/engine/models/unavailable-view-model.ts +26 -5
  362. package/src/server/plugins/engine/options.js +2 -1
  363. package/src/server/plugins/engine/outputFormatters/adapter/v1.ts +18 -7
  364. package/src/server/plugins/engine/outputFormatters/human/v1.ts +2 -1
  365. package/src/server/plugins/engine/outputFormatters/machine/v2.ts +3 -0
  366. package/src/server/plugins/engine/pageControllers/FileUploadPageController.ts +60 -9
  367. package/src/server/plugins/engine/pageControllers/QuestionPageController.ts +2 -2
  368. package/src/server/plugins/engine/pageControllers/RepeatPageController.ts +2 -2
  369. package/src/server/plugins/engine/pageControllers/SummaryPageController.ts +15 -4
  370. package/src/server/plugins/engine/pageControllers/helpers/pages.ts +2 -1
  371. package/src/server/plugins/engine/pageControllers/helpers/submission.ts +143 -4
  372. package/src/server/plugins/engine/pageControllers/validationOptions.ts +29 -4
  373. package/src/server/plugins/engine/plugin.ts +6 -0
  374. package/src/server/plugins/engine/referenceNumbers.ts +2 -0
  375. package/src/server/plugins/engine/routes/index.ts +9 -3
  376. package/src/server/plugins/engine/types/enums.ts +4 -3
  377. package/src/server/plugins/engine/types/schema.ts +22 -5
  378. package/src/server/plugins/engine/types.ts +28 -1
  379. package/src/server/plugins/engine/unavailable-response.ts +6 -2
  380. package/src/server/plugins/engine/views/components/_location-field-base.html +4 -2
  381. package/src/server/plugins/engine/views/components/fileuploadfield.html +1 -1
  382. package/src/server/plugins/engine/views/components/geospatialfield.html +1 -0
  383. package/src/server/plugins/engine/views/components/osgridreffield.html +4 -1
  384. package/src/server/plugins/engine/views/components/trackingfield.html +3 -0
  385. package/src/server/plugins/engine/views/components/ukaddressfield.html +1 -1
  386. package/src/server/plugins/engine/views/file-upload.html +1 -1
  387. package/src/server/plugins/engine/views/summary.html +1 -0
  388. package/src/server/plugins/engine/views/unavailable.html +14 -11
  389. package/src/server/plugins/map/routes/index.js +29 -4
  390. package/src/server/plugins/map/routes/vts/README.md +38 -0
  391. package/src/server/plugins/map/routes/vts/countries-5dp.geojson +4 -0
  392. package/src/server/plugins/map/routes/vts/countries.geojson +10 -39283
  393. package/src/server/plugins/map/types.js +9 -2
  394. package/src/server/plugins/nunjucks/filters/applyUrlParam.js +17 -0
  395. package/src/server/plugins/nunjucks/filters/index.js +1 -0
  396. package/src/server/plugins/payment/helper.js +22 -2
  397. package/src/server/plugins/payment/service.js +6 -2
  398. package/src/server/plugins/payment/types.js +1 -0
  399. package/src/server/plugins/postcode-lookup/models/index.js +91 -46
  400. package/src/server/plugins/postcode-lookup/routes/index.js +96 -24
  401. package/src/server/plugins/postcode-lookup/types.js +6 -1
  402. package/src/server/plugins/postcode-lookup/views/postcode-lookup-details.html +3 -3
  403. package/src/server/utils/type-utils.ts +1 -11
  404. package/src/server/utils/utils.js +19 -1
  405. package/src/typings/geodesy/index.d.ts +18 -0
  406. package/src/typings/hapi/index.d.ts +2 -0
@@ -1,4 +1,5 @@
1
1
  import {
2
+ type ConditionEvaluationOutcome,
2
3
  type ConditionWrapper,
3
4
  type FormComponentsDef,
4
5
  type Section
@@ -16,9 +17,39 @@ import {
16
17
  type FormSubmissionError
17
18
  } from '../types.js'
18
19
 
20
+ /**
21
+ * The result of evaluating a condition, keeping a failed evaluation distinct
22
+ * from one that legitimately returned `false`.
23
+ * @see {@link ExecutableCondition.evaluate}
24
+ */
25
+ export interface ConditionEvaluation {
26
+ outcome: ConditionEvaluationOutcome
27
+ error?: string
28
+ }
29
+
30
+ /**
31
+ * A form condition paired with the parsed expression and callbacks needed to
32
+ * run it against a form submission state
33
+ * Created by `FormModel.makeCondition`
34
+ */
19
35
  export type ExecutableCondition = ConditionWrapper & {
36
+ /**
37
+ * Parsed expression for the condition's {@link ConditionWrapper.value},
38
+ * evaluated against a context built from the submission state
39
+ */
20
40
  expr: Expression
41
+
42
+ /**
43
+ * Evaluates the condition, used for page routing and component visibility
44
+ * A failed evaluation is reported as `false`
45
+ */
21
46
  fn: (evaluationState: FormState) => boolean
47
+
48
+ /**
49
+ * As `fn`, but reports whether evaluation failed rather than defaulting a
50
+ * failure to `false`. Used to record condition outcomes on submission.
51
+ */
52
+ evaluate: (evaluationState: FormState) => ConditionEvaluation
22
53
  }
23
54
 
24
55
  /**
@@ -1,19 +1,40 @@
1
- import { type FormMetadata, type FormMetadataContact } from '@defra/forms-model'
1
+ import {
2
+ type FormDefinition,
3
+ type FormMetadata,
4
+ type FormMetadataContact
5
+ } from '@defra/forms-model'
6
+
7
+ import { createFormTranslator } from '../i18n/createFormTranslator.js'
8
+ import { getAvailableLanguages } from '../i18n/languages.js'
9
+ import { type Translator } from '../types/index.js'
2
10
 
3
11
  export interface UnavailableViewModel {
4
12
  pageTitle: string
5
13
  formTitle: string
6
14
  organisationName: string
7
15
  contact?: FormMetadataContact
16
+ context: { translator: Translator }
17
+ language: string
18
+ languages: { code: string; name: string }[]
8
19
  }
9
20
 
10
21
  export function unavailableViewModel(
11
- metadata: FormMetadata
22
+ metadata: FormMetadata,
23
+ definition: FormDefinition | undefined,
24
+ language: string
12
25
  ): UnavailableViewModel {
26
+ const translator = createFormTranslator(metadata, definition, language)
27
+ const { t, tForm } = translator
13
28
  return {
14
- pageTitle: 'Sorry, this form is unavailable',
15
- formTitle: metadata.title,
29
+ pageTitle: t('pages.formUnavailable.title'),
30
+ formTitle: tForm('title') || metadata.title,
16
31
  organisationName: metadata.organisation,
17
- contact: metadata.contact
32
+ contact: metadata.contact,
33
+ context: { translator },
34
+ language,
35
+ // Always get Welsh and English
36
+ languages: getAvailableLanguages({
37
+ metadata: { translations: { cy: {} } }
38
+ } as unknown as FormDefinition)
18
39
  }
19
40
  }
@@ -26,7 +26,8 @@ const pluginRegistrationOptionsSchema = Joi.object({
26
26
  saveAndExit: Joi.function().optional(),
27
27
  ordnanceSurveyApiKey: Joi.string().optional(),
28
28
  ordnanceSurveyApiSecret: Joi.string().optional(),
29
- getLanguage: Joi.function().optional()
29
+ getLanguage: Joi.function().optional(),
30
+ generateReferenceNumber: Joi.function().optional()
30
31
  })
31
32
 
32
33
  /**
@@ -1,8 +1,10 @@
1
1
  import {
2
+ Engine,
2
3
  type FormMetadata,
3
4
  type SubmitResponsePayload
4
5
  } from '@defra/forms-model'
5
6
 
7
+ import { EN_GB } from '../../../../constants.js'
6
8
  import {
7
9
  getFormVersion,
8
10
  type checkFormStatus
@@ -10,6 +12,7 @@ import {
10
12
  import { type FormModel } from '../../models/FormModel.js'
11
13
  import { type DetailItem } from '../../models/types.js'
12
14
  import { categoriseData } from '../machine/v2.js'
15
+ import { buildConditionEvaluations } from '../../pageControllers/helpers/submission.js'
13
16
  import { FormAdapterSubmissionSchemaVersion } from '../../types/enums.js'
14
17
  import {
15
18
  type FormAdapterSubmissionMessageData,
@@ -42,18 +45,14 @@ export function format(
42
45
  formSlug: formMetadata?.slug ?? '',
43
46
  status: formStatus.state,
44
47
  isPreview: formStatus.isPreview,
45
- notificationEmail: formMetadata?.notificationEmail ?? ''
48
+ notificationEmail: formMetadata?.notificationEmail ?? '',
49
+ language: context.translator?.language ?? EN_GB
46
50
  }
47
51
 
48
52
  if (versionMetadata) {
49
53
  meta.versionMetadata = versionMetadata
50
54
  }
51
55
 
52
- const definitionLanguage = model.def.metadata?.language
53
- if (typeof definitionLanguage === 'string') {
54
- meta.language = definitionLanguage
55
- }
56
-
57
56
  const main = Object.fromEntries(
58
57
  Object.entries(v2Main).map(([key, value]) => {
59
58
  if (value === undefined) {
@@ -76,7 +75,19 @@ export function format(
76
75
  const payload: FormAdapterSubmissionMessagePayload = {
77
76
  meta,
78
77
  data,
79
- result
78
+ result,
79
+
80
+ // Recorded here because only the engine holds the walked evaluation state
81
+ // the conditions were judged against; forms-notify-listener receives the
82
+ // flat submitted answers and resolves the outputs that qualify from these
83
+ // outcomes alone. Condition ids are only stable in V2, so a V1 definition
84
+ // has nothing to report - the property is still emitted, because its
85
+ // absence is what marks a message as predating this and sends the listener
86
+ // down its legacy path.
87
+ conditionEvaluations:
88
+ model.engine === Engine.V2
89
+ ? buildConditionEvaluations(model, context)
90
+ : []
80
91
  }
81
92
 
82
93
  return JSON.stringify(payload)
@@ -5,6 +5,7 @@ import {
5
5
  import { addDays, format as dateFormat } from 'date-fns'
6
6
 
7
7
  import { config } from '../../../../../config/index.js'
8
+ import { EN_GB } from '../../../../constants.js'
8
9
  import { getAnswer } from '../../components/helpers/components.js'
9
10
  import { escapeMarkdown } from '../../components/helpers/index.js'
10
11
  import { PaymentField } from '../../components/index.js'
@@ -116,7 +117,7 @@ function appendPaymentSection(paymentItems: DetailItem[], lines: string[]) {
116
117
 
117
118
  const formattedAmount = formatCurrency(paymentState.amount)
118
119
  const dateOfPayment = paymentState.preAuth?.createdAt
119
- ? formatPaymentDate(paymentState.preAuth.createdAt)
120
+ ? formatPaymentDate(paymentState.preAuth.createdAt, EN_GB)
120
121
  : ''
121
122
 
122
123
  lines.push(
@@ -28,6 +28,7 @@ interface PaymentOutput {
28
28
  reference: string
29
29
  amount: number
30
30
  description: string
31
+ descriptionInEng?: string
31
32
  createdAt: string
32
33
  }
33
34
 
@@ -93,6 +94,7 @@ export function format(
93
94
  * reference: 'REF-123',
94
95
  * amount: 10.00,
95
96
  * description: 'Application fee',
97
+ * descriptionInEng: 'Applicaiton fee',
96
98
  * createdAt: '2025-01-23T10:30:00.000Z'
97
99
  * }
98
100
  * }
@@ -204,6 +206,7 @@ function extractPayment(
204
206
  reference: paymentState.reference,
205
207
  amount: paymentState.amount,
206
208
  description: paymentState.description,
209
+ descriptionInEng: item.field.def.options.description,
207
210
  createdAt: paymentState.preAuth?.createdAt ?? ''
208
211
  }
209
212
  }
@@ -42,6 +42,7 @@ import {
42
42
  type FormRequestPayload,
43
43
  type FormResponseToolkit
44
44
  } from '../../../routes/types.js'
45
+ import { joinWithOr } from '../../../utils/utils.js'
45
46
 
46
47
  const MAX_UPLOADS = 25
47
48
  const CDP_UPLOAD_TIMEOUT_MS = 60000 // 1 minute
@@ -188,17 +189,19 @@ export class FileUploadPageController extends QuestionPageController {
188
189
 
189
190
  const { filename } = fileToRemove.status.form.file
190
191
 
191
- const { t } = this.getTranslator(request)
192
+ const translator = this.getTranslator(request)
193
+ const { t } = translator
192
194
 
193
195
  return h.view(this.fileDeleteViewName, {
194
196
  ...viewModel,
195
197
  context,
196
198
  backLink: this.getBackLink(request, context, t),
197
- pageTitle: `Are you sure you want to remove this file?`,
199
+ pageTitle: t('pages.fileUpload.removeFileTitle'),
198
200
  itemTitle: filename,
199
- confirmation: { text: 'You cannot recover removed files.' },
200
- buttonConfirm: { text: 'Remove file' },
201
- buttonCancel: { text: 'Cancel' }
201
+ confirmation: { text: t('pages.fileUpload.removeFileBody') },
202
+ buttonConfirm: { text: t('pages.fileUpload.removeFileButton') },
203
+ buttonCancel: { text: t('pages.fileUpload.cancel') },
204
+ t
202
205
  } satisfies ItemDeletePageViewModel)
203
206
  }
204
207
  }
@@ -224,7 +227,7 @@ export class FileUploadPageController extends QuestionPageController {
224
227
  }
225
228
  }
226
229
 
227
- getErrors(details?: ValidationErrorItem[]) {
230
+ getErrors(details?: ValidationErrorItem[], language?: string) {
228
231
  const { fileUpload } = this
229
232
 
230
233
  if (details) {
@@ -237,7 +240,7 @@ export class FileUploadPageController extends QuestionPageController {
237
240
  if (!isUploadError || isUploadRootError) {
238
241
  // The error is for the root of the upload or another
239
242
  // field on the page so defer to the getError helper
240
- errors.push(getError(error))
243
+ errors.push(getError(language, error))
241
244
  } else {
242
245
  const { context, path, type } = error
243
246
 
@@ -266,6 +269,7 @@ export class FileUploadPageController extends QuestionPageController {
266
269
  ): FeaturedFormPageViewModel {
267
270
  const { fileUpload } = this
268
271
  const { state } = context
272
+ const { t } = translator
269
273
 
270
274
  const upload = this.getUploadFromState(state)
271
275
 
@@ -277,6 +281,19 @@ export class FileUploadPageController extends QuestionPageController {
277
281
  ({ model }) => model.id === fileUpload.name
278
282
  )
279
283
 
284
+ formComponent.model.upload = {
285
+ count: formComponent.model.upload?.count ?? 0,
286
+ summaryList: formComponent.model.upload?.summaryList ?? { rows: [] },
287
+ uploadingLabel: t('components.fileUploadField.uploading')
288
+ }
289
+ formComponent.model = {
290
+ ...formComponent.model,
291
+ javascript: true,
292
+ chooseFilesButtonText: t('components.fileUploadField.chooseFile'),
293
+ dropInstructionText: t('components.fileUploadField.dropFile'),
294
+ noFileChosenText: t('components.fileUploadField.noFileChosen')
295
+ }
296
+
280
297
  const index = components.indexOf(formComponent)
281
298
 
282
299
  const proxyUrl = getProxyUrlForLocalDevelopment(upload?.uploadUrl)
@@ -290,7 +307,8 @@ export class FileUploadPageController extends QuestionPageController {
290
307
  // Split out components before/after
291
308
  componentsBefore: components.slice(0, index),
292
309
  components: components.slice(index),
293
- proxyUrl
310
+ proxyUrl,
311
+ t
294
312
  }
295
313
  }
296
314
 
@@ -441,6 +459,9 @@ export class FileUploadPageController extends QuestionPageController {
441
459
 
442
460
  const allErrors: FormSubmissionError[] = []
443
461
 
462
+ const translator = this.getTranslator(request)
463
+ const { t } = translator
464
+
444
465
  for (const file of uploadedFiles) {
445
466
  if (file.fileStatus === FileStatus.complete) {
446
467
  const perFileState: FileState = {
@@ -455,7 +476,14 @@ export class FileUploadPageController extends QuestionPageController {
455
476
  // Collect the error for rejected/pending files.
456
477
  const { fileUpload } = this
457
478
  const name = fileUpload.name
458
- const text = file.errorMessage ?? 'Unknown error'
479
+ const errorParams = this.expandErrorParams(
480
+ file.errorParams,
481
+ t('common.or')
482
+ )
483
+ const text = t(
484
+ `pages.fileUpload.${file.errorCode ?? 'FILE_ERROR_UNKNOWN'}`,
485
+ errorParams
486
+ )
459
487
  allErrors.push({ path: [name], href: `#${name}`, name, text })
460
488
  }
461
489
  }
@@ -472,6 +500,29 @@ export class FileUploadPageController extends QuestionPageController {
472
500
  }
473
501
  }
474
502
 
503
+ /**
504
+ * Process the error parameters so any arrays get expanded (joined), ready to be passed into the error message
505
+ * @param errorParams - received from the file upload (may be a number, a string, or a string array)
506
+ * @param finalSeparator - 'or' in English, and 'neu' in Welsh
507
+ */
508
+ private expandErrorParams(
509
+ errorParams: Record<string, number | string | string[]> | undefined,
510
+ finalSeparator: string
511
+ ) {
512
+ if (!errorParams) {
513
+ return {}
514
+ }
515
+ const expanded = {} as Record<string, number | string>
516
+ for (const [key, value] of Object.entries(errorParams)) {
517
+ if (Array.isArray(value)) {
518
+ expanded[key] = joinWithOr(value, finalSeparator)
519
+ } else {
520
+ expanded[key] = value
521
+ }
522
+ }
523
+ return expanded
524
+ }
525
+
475
526
  /**
476
527
  * Checks the payload for a file getting removed
477
528
  * and removes it from the upload files if found
@@ -364,8 +364,8 @@ export class QuestionPageController extends PageController {
364
364
  return this.collection.getStateFromValidForm(payload)
365
365
  }
366
366
 
367
- getErrors(details?: ValidationErrorItem[]) {
368
- return getErrors(details)
367
+ getErrors(details?: ValidationErrorItem[], language?: string) {
368
+ return getErrors(language, details)
369
369
  }
370
370
 
371
371
  async getState(request: AnyFormRequest) {
@@ -406,9 +406,9 @@ export class RepeatPageController extends QuestionPageController {
406
406
  const { query } = request
407
407
  const { isForceAccess, errors } = context
408
408
 
409
- const { t } = translator
409
+ const { t, tPage } = translator
410
410
 
411
- const { title } = repeat.options
411
+ const title = tPage(this.pageDef, 'repeatTitle') || repeat.options.title
412
412
 
413
413
  const summaryList: SummaryList = {
414
414
  classes: 'govuk-summary-list--long-actions',
@@ -114,7 +114,7 @@ export class SummaryPageController extends QuestionPageController {
114
114
  viewModel.paymentDetails = this.buildPaymentDetails(
115
115
  paymentField,
116
116
  paymentState,
117
- t
117
+ translator
118
118
  )
119
119
  }
120
120
 
@@ -151,8 +151,9 @@ export class SummaryPageController extends QuestionPageController {
151
151
  paymentState: NonNullable<
152
152
  ReturnType<PaymentField['getPaymentStateFromState']>
153
153
  >,
154
- t: (key: string, opts?: Record<string, unknown>) => string
154
+ translator: Translator
155
155
  ) {
156
+ const { t } = translator
156
157
  const rows = [
157
158
  {
158
159
  key: { text: t('pages.summary.paymentFor') },
@@ -171,7 +172,12 @@ export class SummaryPageController extends QuestionPageController {
171
172
  if (paymentState.preAuth?.createdAt) {
172
173
  rows.push({
173
174
  key: { text: t('pages.summary.dateOfPayment') },
174
- value: { text: formatPaymentDate(paymentState.preAuth.createdAt) }
175
+ value: {
176
+ text: formatPaymentDate(
177
+ paymentState.preAuth.createdAt,
178
+ translator.language
179
+ )
180
+ }
175
181
  })
176
182
  }
177
183
 
@@ -557,7 +563,12 @@ function submitData(
557
563
  retrievalKey,
558
564
  referenceNumber,
559
565
  main: buildMainRecords(items, translator),
560
- repeaters: buildRepeaterRecords(items, translator)
566
+ repeaters: buildRepeaterRecords(items, translator),
567
+ // Only populate the language property if the form is multi-language enabled
568
+ // @ts-expect-error - dynamic language property
569
+ language: model.def.metadata?.translations?.cy
570
+ ? translator.language
571
+ : undefined
561
572
  }
562
573
  return submit(payload)
563
574
  }
@@ -4,6 +4,7 @@ import {
4
4
  type Page
5
5
  } from '@defra/forms-model'
6
6
 
7
+ import { UnknownPageControllerError } from '../../errors.js'
7
8
  import { type FormModel } from '../../models/index.js'
8
9
  import * as PageControllers from '../index.js'
9
10
 
@@ -66,7 +67,7 @@ export function createPage(model: FormModel, pageDef: Page) {
66
67
  }
67
68
 
68
69
  if (typeof controller === 'undefined') {
69
- throw new Error(`Page controller ${pageDef.controller} does not exist`)
70
+ throw new UnknownPageControllerError(pageDef.controller)
70
71
  }
71
72
 
72
73
  return controller
@@ -1,13 +1,27 @@
1
- import { type SubmitPayload } from '@defra/forms-model'
1
+ import {
2
+ isConditionWrapperV2,
3
+ type ConditionDataV2,
4
+ type ConditionRefDataV2,
5
+ type ConditionWrapperV2,
6
+ type SubmitConditionEvaluation,
7
+ type SubmitConditionReference,
8
+ type SubmitPayload
9
+ } from '@defra/forms-model'
2
10
 
3
11
  import { GeospatialField } from '../../components/GeospatialField.js'
4
12
  import { PaymentField } from '../../components/PaymentField.js'
5
13
  import { getAnswer } from '../../components/helpers/components.js'
6
14
  import { type Translator } from '../../i18n/types.js'
15
+ import { type FormModel } from '../../models/FormModel.js'
7
16
  import {
8
17
  type DetailItem,
9
18
  type DetailItemField
10
19
  } from '../../models/types.js'
20
+ import {
21
+ type FormContext,
22
+ type FormState,
23
+ type FormStateValue
24
+ } from '../../types.js'
11
25
  import {
12
26
  formatCurrency,
13
27
  formatPaymentDate
@@ -36,7 +50,7 @@ export function buildMainRecords(
36
50
 
37
51
  for (const item of fieldItems) {
38
52
  if (item.field instanceof PaymentField) {
39
- records.push(...buildPaymentRecords(item))
53
+ records.push(...buildPaymentRecords(item, translator))
40
54
  } else if (item.field instanceof GeospatialField) {
41
55
  // Stringify of GeoJSON is done here rather than inside `getContextValueFromState`
42
56
  // so we don't incur the overhead of JSON.stringify on every request when building context
@@ -67,7 +81,10 @@ export function buildMainRecords(
67
81
  *
68
82
  * Returns an empty array if no payment state exists.
69
83
  */
70
- export function buildPaymentRecords(item: DetailItemField): SubmitRecord[] {
84
+ export function buildPaymentRecords(
85
+ item: DetailItemField,
86
+ translator: Translator
87
+ ): SubmitRecord[] {
71
88
  const paymentState = (item.field as PaymentField).getPaymentStateFromState(
72
89
  item.state
73
90
  )
@@ -96,7 +113,7 @@ export function buildPaymentRecords(item: DetailItemField): SubmitRecord[] {
96
113
  name: `${item.name}_paymentDate`,
97
114
  title: 'Payment date',
98
115
  value: paymentState.preAuth?.createdAt
99
- ? formatPaymentDate(paymentState.preAuth.createdAt)
116
+ ? formatPaymentDate(paymentState.preAuth.createdAt, translator.language)
100
117
  : ''
101
118
  }
102
119
  ]
@@ -138,3 +155,125 @@ export function buildRepeaterRecords(
138
155
  )
139
156
  }))
140
157
  }
158
+
159
+ /**
160
+ * Records the outcome of every condition in the form definition, evaluated
161
+ * against the answers as they stand at the point of submission.
162
+ *
163
+ * Evaluated here rather than by the consumer because only the engine holds
164
+ * the context to evaluate against: `evaluationState` is built by walking the
165
+ * form from its start page along the path taken, applying page conditions as
166
+ * it goes. A consumer receives the flat submitted answers, not that walked
167
+ * state, and reproducing the walk in a second codebase would leave two
168
+ * implementations of the same logic free to diverge.
169
+ *
170
+ * Each record carries the components the condition depends on and whether each
171
+ * was answered. An unanswered question still yields a boolean - negative
172
+ * operators such as "is not" return `true` against the seeded `null` - so the
173
+ * outcome alone cannot be read as evidence that the user gave that answer.
174
+ *
175
+ * V2 definitions only. `conditionId` is the V2 condition id, and the
176
+ * references are resolved from the component ids V2 conditions carry - V1
177
+ * conditions reference components by name, and V1 components need not have
178
+ * an id at all.
179
+ */
180
+ export function buildConditionEvaluations(
181
+ model: FormModel,
182
+ context: FormContext
183
+ ): SubmitConditionEvaluation[] {
184
+ const { evaluationState } = context
185
+
186
+ return model.def.conditions
187
+ .filter(isConditionWrapperV2)
188
+ .flatMap((conditionDef) => {
189
+ const condition = model.conditions[conditionDef.id]
190
+
191
+ if (!condition) {
192
+ return []
193
+ }
194
+
195
+ const { outcome } = condition.evaluate(evaluationState)
196
+
197
+ const references = collectReferences(model, conditionDef, evaluationState)
198
+
199
+ return {
200
+ conditionId: conditionDef.id,
201
+ outcome,
202
+ references: [...references.values()]
203
+ }
204
+ })
205
+ }
206
+
207
+ /**
208
+ * Whether a component held an answer at the point a condition was evaluated.
209
+ *
210
+ * The engine seeds every component in `evaluationState` with `null` before the
211
+ * page walk begins, so an unanswered question is present but empty rather than
212
+ * absent.
213
+ * @see {@link FormModel.initialiseContext}
214
+ */
215
+ export function isAnswered(value: FormStateValue | undefined) {
216
+ if (value === undefined || value === null) {
217
+ return false
218
+ }
219
+
220
+ if (Array.isArray(value)) {
221
+ return value.length > 0
222
+ }
223
+
224
+ return value !== ''
225
+ }
226
+
227
+ function isConditionDataV2(
228
+ item: ConditionDataV2 | ConditionRefDataV2
229
+ ): item is ConditionDataV2 {
230
+ return 'componentId' in item
231
+ }
232
+
233
+ /**
234
+ * Recursive function that collects every component a condition depends on, following nested condition
235
+ * references. Results are keyed by component id so a component referenced more
236
+ * than once is reported once.
237
+ */
238
+ function collectReferences(
239
+ model: FormModel,
240
+ conditionDef: ConditionWrapperV2,
241
+ evaluationState: FormState,
242
+ references: Map<string, SubmitConditionReference> = new Map<
243
+ string,
244
+ SubmitConditionReference
245
+ >(),
246
+ visited: Set<string> = new Set<string>()
247
+ ) {
248
+ if (visited.has(conditionDef.id)) {
249
+ return references
250
+ }
251
+
252
+ visited.add(conditionDef.id)
253
+
254
+ for (const item of conditionDef.items) {
255
+ if (isConditionDataV2(item)) {
256
+ const component = model.getComponentById(item.componentId)
257
+
258
+ // A condition referencing a component that no longer exists cannot be
259
+ // resolved to a name, so there is nothing meaningful to report for it
260
+ if (component) {
261
+ references.set(item.componentId, {
262
+ componentId: item.componentId,
263
+ componentName: component.name,
264
+ answered: isAnswered(evaluationState[component.name])
265
+ })
266
+ }
267
+
268
+ continue
269
+ }
270
+
271
+ const referenced = model.getConditionById(item.conditionId)
272
+
273
+ if (referenced) {
274
+ collectReferences(model, referenced, evaluationState, references, visited)
275
+ }
276
+ }
277
+
278
+ return references
279
+ }
@@ -26,7 +26,7 @@ export const opts = {
26
26
  * import messageTemplate directly and get the English version.
27
27
  */
28
28
  export const messageTemplate: ValidationMessages = buildValidationMessages(
29
- (key) => t(key, 'en-GB')
29
+ (key, options) => t(key, 'en-GB', options)
30
30
  )
31
31
 
32
32
  export const messages: LanguageMessagesExt = {
@@ -58,14 +58,28 @@ export const messages: LanguageMessagesExt = {
58
58
  'date.min': messageTemplate.dateMin,
59
59
  'date.max': messageTemplate.dateMax,
60
60
 
61
- 'object.invalidjson': messageTemplate.format
61
+ 'array.min': messageTemplate.arrayMin,
62
+ 'array.max': messageTemplate.arrayMax,
63
+ 'array.length': messageTemplate.arrayLength,
64
+
65
+ 'features.min': messageTemplate.featuresMin,
66
+ 'features.max': messageTemplate.featuresMax,
67
+ 'features.length': messageTemplate.featuresLength,
68
+
69
+ 'files.min': messageTemplate.filesMin,
70
+ 'files.max': messageTemplate.filesMax,
71
+ 'filees.length': messageTemplate.filesLength,
72
+
73
+ 'object.invalidjson': messageTemplate.format,
74
+
75
+ 'phoneNumber.invalid': messageTemplate.format
62
76
  }
63
77
 
64
78
  export const messagesPre: LanguageMessages =
65
79
  messages as unknown as LanguageMessages
66
80
 
67
81
  export function buildLanguageMessages(
68
- t: (key: string) => string
82
+ t: (key: string, options?: Record<string, unknown>) => string
69
83
  ): LanguageMessages {
70
84
  const vm = buildValidationMessages(t)
71
85
  return {
@@ -76,6 +90,7 @@ export function buildLanguageMessages(
76
90
  'string.email': vm.format,
77
91
  'string.pattern.base': vm.pattern,
78
92
  'string.maxWords': vm.maxWords,
93
+ 'string.unicode': vm.unicode,
79
94
  'number.base': vm.number,
80
95
  'number.precision': vm.numberPrecision,
81
96
  'number.integer': vm.numberInteger,
@@ -91,7 +106,17 @@ export function buildLanguageMessages(
91
106
  'date.format': vm.dateFormat,
92
107
  'date.min': vm.dateMin,
93
108
  'date.max': vm.dateMax,
94
- 'object.invalidjson': vm.format
109
+ 'array.min': vm.arrayMin,
110
+ 'array.max': vm.arrayMax,
111
+ 'array.length': vm.arrayLength,
112
+ 'features.min': vm.featuresMin,
113
+ 'features.max': vm.featuresMax,
114
+ 'features.length': vm.featuresLength,
115
+ 'files.min': vm.filesMin,
116
+ 'files.max': vm.filesMax,
117
+ 'files.length': vm.filesLength,
118
+ 'object.invalidjson': vm.format,
119
+ 'phoneNumber.invalid': vm.format
95
120
  } as unknown as LanguageMessages
96
121
  }
97
122